JUCE 中的音频编解码

JUCE 中的音频编解码

JUCE (Jules’ Utility Class Extensions)是由Raw MaterialSoftware发布的一套基于C++的跨平台应用程序框架类库(Windows, Mac,Linux)。JUCE的特殊之处在于其友好的用户界面以及强大的音频、图像处理能力。JUCE适合那些想使用干净、快捷、高层的API,而不想 把时间浪费在使用不同类库,面向不同平台上的开发者。JUCE能够胜任大型、复杂的应用程序(C++)的开发。
与其他应用程序框架类似,JUCE有众多覆盖音频、图像、XML分析、网络等方面的类。JUCE的开发者就是被JAVA的JDK所启发,然后想做出基于C++的对等物。
JUCE最突出的特点是其对音频的特殊支持。JUCE原本是作为Tracktion audiosequencer的一部分而开发的,但后来脱离出来成为了一个独立的工程。JUCE支持音频和MIDI回放,复音合成器,对多种音频格式文件 的读取。同时,JUCE还封装了诸如VST、RTAS、AU等技术的代码,能够开发各种音源、效果插件。众多音频厂商如Imageline、M-AUDIO、cycling74、KORG、Presonus、TC Group等等都是JUCE的使用者。

JUCE 对音频编解码做了很好的支持,我们关心如何利用 JUCE 进行音频文件在读取与写入。这篇文章中,通过丰富的例子来说明如何使用 JUCE 中的编解码工具。

File,基础文件操作

在介绍编解码前,让我们先来看看基本的文件操作。

在 JUCE 中,File 类用来表示一个文件或者目录,它提供了文件相关的基础操作,例如:

  • exitst(),判断文件是否存在
  • isDirectory(),是否为文件夹
  • getFileExtension(),获取文件扩展名
  • create(),创建文件
  • deleteFile(),删除文件
  • createInputStream() 创建可读文件流
  • createOutputStream() 创建可写文件流

详细的接口说明请查阅 Class File,下面举个例子,读入 txt 文件并打印

const string path_to_file;
File file(path_to_file);
if(file.existsAsFile()){
    auto content = file.loadFileAsString();
    cout << content << endl;
}

AudioFormat,负责音频解码

在 JUCE 中负责解码音频文件类都继承了 AudioFormat,你可以在 juce_audio_formats/codecs 找到相应的代码,从文件目录来看,在 Windows 下用的是 Windows Media 系统 API,在 Mac/iOS 下用的是 Core Audio,此外还支持以下格式:

  • wav
  • mp3
  • ogg
  • flac

AudioFormat 中有几个接口我们需要熟悉

  • getPossibleSampleRates() 返回该格式支持的采样率
  • getPossibleBitDepths() 返回该格式支持的位深
  • getQualityOptions() 返回一组字符串,用于描述写入音频的质量信息
  • createReaderFor() 创建 AudioFormatReader 用于读取音频数据
  • createWriterFor() 创建 AudioFormatWriter 用于写入音频数据

我们可以打印 AudioFormat 的基本信息

void printfAudioFormatInfo(AudioFormat* format)
{
    auto format_name = format->getFormatName();
    auto possible_extensions = format->getFileExtensions();
    auto possible_sample_rates = format->getPossibleSampleRates();
    auto possible_bit_depths = format->getPossibleBitDepths();
    auto quality_opts = format->getQualityOptions();

    cout << "format name:" << format->getFormatName() << endl;
    cout << "possible extensions:\n";
    for(const auto& item : possible_extensions){
        cout << "\t" << item << endl;
    }
    cout << "possible sample rates:\n";
    for(const auto& item : possible_sample_rates){
        cout << "\t" << item << endl;
    }
    cout << "possible bit depth:\n";
    for(const auto& item : possible_bit_depths){
        cout << "\t" << item << endl;
    }
    cout << "quality options:\n";
    for(const auto& item : quality_opts){
        cout << "\t" << item << endl;
    }
    cout << "is compressed: " << (format->isCompressed() ? "YES" : "NO");
}

WavAudioFormat

WavAudioFormat 负责 wave 读取与写入。它的基本信息包括:

format name:WAV file
possible extensions:
	.wav
	.bwf
possible sample rates:
	8000
	11025
	12000
	16000
	22050
	32000
	44100
	48000
	88200
	96000
	176400
	192000
	352800
	384000
possible bit depth:
	8
	16
	24
	32
quality options:
is compressed: NO

读取 wav 数据

void readFromAudioFormat(const std::string& path_to_file, AudioFormat* format)
{
    File audio_file {path_to_file};
    if(audio_file.existsAsFile()){
        auto in = audio_file.createInputStream();
        AudioFormatReader* reader = format->createReaderFor(in.release(), true);
        if(!reader){
            cerr << "cannot create AudioFormatReader" << endl;
            return;
        }

        // print wave file information
        cout << "num channels:" << reader->numChannels << endl;
        cout << "sample rate:" << reader->sampleRate << endl;
        cout << "length:" << reader->lengthInSamples << endl;

        // read left channel data
        const auto num_read = reader->lengthInSamples;
        std::vector<float> left_buffer(num_read);
        float* data_refer_to[1] = {left_buffer.data()};
        reader->read(data_refer_to, 1, 0, num_read);

        delete reader;
    }
}

WavAudioFormat wav;
readFromAudioFormat("path_to_wave_file", &wav);

在读取 wav 文件时,

  1. 通过 File::createInputStream() 创建文件流
  2. WavAudioFormat::createReaderFor() 通过文件流创建 AudioFormatReader,接下来,所有音频文件解码、读取的操作都委托给它。注意一个细节:in.release() 将文件流所有权交给 AudioFormatReader 进行管理,AudioFormatReader 在析构时会同时释放流
  3. 通过 AudioFormatReader 可以获取音频文件信息,例如采样率、声道数、音频长度等
  4. 接着,通过 read 方法读取音频数据
  5. 最后,记得 delete reader 来释放资源,同时关闭文件流

通过 MemoryMappedReader 读取 wav 数据

对 wav 此类没有压缩的格式,通过一些技术手段进行加速读取速度,其中 mmap 是最方便的。

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。

关于 mmap 详细介绍请参考 认真分析mmap:是什么 为什么 怎么用,或者参考 mapread.c 学习如何使用 mmap

下面的代码展示了如何用 MemoryMappedReader 读取数据。注意,需要调用 reader->mapEntireFile() 主动进行内存映射。

void readFromAudioFormatMapReader(const std::string& path_to_file, AudioFormat* format)
{
    File audio_file {path_to_file};
    if(audio_file.existsAsFile()){
        auto in = audio_file.createInputStream();
        auto* reader = format->createMemoryMappedReader(audio_file);
        if(!reader){
            cerr << "cannot create AudioFormatReader" << endl;
            return;
        }

        // map entire file into memory
        reader->mapEntireFile();

        // print wave file information
        cout << "num channels:" << reader->numChannels << endl;
        cout << "sample rate:" << reader->sampleRate << endl;
        cout << "length:" << reader->lengthInSamples << endl;

        // read left channel data
        const auto num_read = reader->lengthInSamples;
        std::vector<float> left_buffer(num_read);
        float* data_refer_to[1] = {left_buffer.data()};

        reader->read(data_refer_to, 1, 0, num_read);

        delete reader;
    }
}

与普通的读取性能上有什么区别呢?在读取 4min 音频上,在我的电脑上,两者性能差异约为 25 倍。这提升还是相当可观的,但是对于较短的音频,性能提升就不太明显,例如 10s 音频,提升大概 10% 不到。

在 JUCE 中,可以使用 MemoryMappedReader 进行读取数据的格式只有两种,wav 和后面会提到的 aiff。

写入 wav 数据

void writeByAudioFormat(const std::string& output_path, AudioFormat* format, int bits_per_sample, const StringPairArray& metadataValues, int quality_index)

{
    const double sample_rate_out = 44100;
    const unsigned int num_channels_out = 2;
    File audio_file{output_path};
    auto out = audio_file.createOutputStream();

    AudioFormatWriter* writer = format->createWriterFor(out.release(),
                                                        sample_rate_out,
                                                        num_channels_out,
                                                        bits_per_sample,
                                                        metadataValues,
                                                        quality_index);

    // write 440 sine wave
    const float hz = 440.0f;
    const float tincr = 2 * M_PI * hz / sample_rate_out;
    const int num_output_frame = 200;
    const int num_buffer = 1024;
    std::vector<float> left_buffer(num_buffer);
    std::vector<float> right_buffer(num_buffer);
    float* data_refer_to[2] = {left_buffer.data(), right_buffer.data()};

    float t = 0;
    for(int i = 0; i < num_output_frame; ++i){
        for(int j = 0; j < num_buffer; ++j){
            left_buffer[j] = sin(t);
            right_buffer[j] = left_buffer[j];

            t += tincr;
        }

        writer->writeFromFloatArrays(data_refer_to, num_channels_out, num_buffer);
    }

    delete writer;
}

WavAudioFormat wav;
writeByAudioFormat("./sin440.wav", &wav, 16, {}, 0);

在写入 wave 文件时,

  1. 首先创建 File,并通过 createOutputStream 创建文件的输出流。
  2. 然后,设置输出的采样率、声道数、位深等信息,通过 createWriterFor 创建 AudioFormatWritter,注意一个细节:out.release() 将文件流所有权交给 AudioFormatWritter 进行管理。AudioFormatWritter 在析构时会同时释放流
  3. 接着,调用 writeFromFloatArrays 将音频数据写入文件中
  4. 最后记得 delete writer 进行释放

MP3AudioFormat

MP3AudioFormat 负责 MP3 读取。它的基本信息包括:

format name:MP3 file
possible extensions:
	.mp3
possible sample rates:
possible bit depth:
quality options:
is compressed: YES

MP3AudioFormat::getPossibleSampleRates 返回空列表,似乎表明它能够支持所有采样率。

想要使用 MP3AudioFormat 需要定义宏 JUCE_USE_MP3AUDIOFORMAT。具体操作,可以在 Projucer 的工程设置 -> Preprocessor Definitions 中添加 JUCE_USE_MP3AUDIOFORMAT

读取 MP3 数据

流程与读取 wav 数据一致,不再赘述

MP3AudioFormat mp3;
readFromAudioFormat("path_to_mp3_file", &mp3);

写入 MP3 数据

很遗憾,MP3AudioFormat 不支持写入数据,其 createWriterFor 的实现为空

AudioFormatWriter* MP3AudioFormat::createWriterFor (OutputStream*, double,
                                                    unsigned int, int
                                                    const StringPairArray&, int)
{
    jassertfalse; // not yet implemented!
    return nullptr;
}

LAMEEncoderAudioFormat

LAMEEncoderAudioFormat 它不能读取 MP3 数据,只能写入。内部实现中,它先将数据写入到 WAVE 文件中,接着通过 lame 程序将 WAVE 转换成 MP3。这操作我也是看傻了,似乎是因为 license 的原因。

它的基本操作包括:

format name:MP3 file
possible extensions:
	.mp3
possible sample rates:
	32000
	44100
	48000
possible bit depth:
	16
quality options:
	VBR quality 0 (best)
	VBR quality 1
	VBR quality 2
	VBR quality 3
	VBR quality 4 (normal)
	VBR quality 5
	VBR quality 6
	VBR quality 7
	VBR quality 8
	VBR quality 9 (smallest)
	32 Kb/s CBR
	40 Kb/s CBR
	48 Kb/s CBR
	56 Kb/s CBR
	64 Kb/s CBR
	80 Kb/s CBR
	96 Kb/s CBR
	112 Kb/s CBR
	128 Kb/s CBR
	160 Kb/s CBR
	192 Kb/s CBR
	224 Kb/s CBR
	256 Kb/s CBR
	320 Kb/s CBR
is compressed: YES

想要使用 LAMEEncoderAudioFormat 需要定义宏 JUCE_USE_LAME_AUDIO_FORMAT

写入 MP3 数据

File lame_exe_file{"/usr/local/bin/lame"};
if(lame_exe_file.existsAsFile()){
    LAMEEncoderAudioFormat lame(lame_exe_file);
    writeByAudioFormat("./sin440.mp3", &lame, 16, {}, 0);
}

使用 LAMEEncoderAudioFormat 需要搭配 lame 使用,在 MacOS 下你可以使用 brew install lame 来安装它。

在使用的过程中,JUCE 打印的 log 可以让你清楚的了解它是如何调用 lame 的,例如:

/usr/local/bin/lame --quiet --vbr-new -V 0 /Users/admin/Library/Caches/LAMEENcoderAudioFormatTest/temp_757173c.wav /Users/admin/Library/Caches/LAMEENcoderAudioFormatTest/temp_a5bd89c.mp3

OggVorbisAudioFormat

OggVorbisAudioFormat 负责 ogg 读取与写入。它的基本信息包括:

format name:Ogg-Vorbis file
possible extensions:
	.ogg
possible sample rates:
	8000
	11025
	12000
	16000
	22050
	32000
	44100
	48000
	88200
	96000
	176400
	192000
possible bit depth:
	32
quality options:
	64 kbps
	80 kbps
	96 kbps
	112 kbps
	128 kbps
	160 kbps
	192 kbps
	224 kbps
	256 kbps
	320 kbps
	500 kbps
is compressed: YES

想要使用 OggVorbisAudioFormat 需要定义宏 JUCE_USE_OGGVORBIS

OggVorbisAudioFormat 的读取与写入流程与 WavAudioFormat 一致,没有其他惊喜,不再赘述。

OggVorbisAudioFormat ogg;

readFromAudioFormat(“path_to_ogg_file.ogg”, &ogg);
writeByAudioFormat("./sin440.ogg", &ogg, 32, {}, 0);

FlacAudioFormat

FlacAudioFormat 负责 flac 读取与写入。它的基本信息包括:

format name:FLAC file
possible extensions:
	.flac
possible sample rates:
	8000
	11025
	12000
	16000
	22050
	32000
	44100
	48000
	88200
	96000
	176400
	192000
	352800
	384000
possible bit depth:
	16
	24
quality options:
	0 (Fastest)
	1
	2
	3
	4
	5 (Default)
	6
	7
	8 (Highest quality)
is compressed: YES

想要使用 FlacAudioFormat 需要定义宏 JUCE_USE_FLAC

FlacAudioFormat 的读取与写入流程与 WavAudioFormat 一致,没有其他惊喜,不再赘述。

FlacAudioFormat flac;

readFromAudioFormat("path_to_flac_file.flac", &flac);
writeByAudioFormat("./sin440.flac", &flac, 16, {}, 0);

AiffAudioFormat

AiffAudioFormat 负责 aiff 读取与写入。它的基本信息包括:

format name:AIFF file
possible extensions:
	.aiff
	.aif
possible sample rates:
	22050
	32000
	44100
	48000
	88200
	96000
	176400
	192000
possible bit depth:
	8
	16
	24
quality options:
is compressed: NO

AiffAudioFormat 的读取与写入流程与 WavAudioFormat 一致,没有其他惊喜,不再赘述。

AiffAudioFormat aiff;

readFromAudioFormat("path_to_aiff_file.aif", &aiff);
writeByAudioFormat("./sin440.aif", &aiff, 16, {}, 0);

CoreAudioFormat

CoreAudioFormat 仅在 OSX 和 iOS 系统中可用,它使用 AudioToolbox 框架来读取文件。在笔者的 mac 上,它的基本信息包括:

format name:CoreAudio supported file
possible extensions:
	.mp2
	.xhe
	.amr
	.ac3
	.loas
	.qt
	.adts
	.mp3
	.m4r
	.wav
	.3gp
	.mpa
	.sd2
	.mpeg
	.ec3
	.m4a
	.mp4
	.mov
	.snd
	.aifc
	.caf
	.flac
	.m4b
	.3g2
	.mp1
	.aac
	.aiff
	.aif
	.au
	.latm
possible sample rates:
possible bit depth:
quality options:
is compressed: NO

可以看到 CoreAudioFormat 支持解码的格式非常丰富,但是很遗憾,CoreAudioFormat 不支持写入文件,createWriterFor 实现为空

AudioFormatWriter* CoreAudioFormat::createWriterFor (OutputStream*,
                                                     double /*sampleRateToUse*/,
                                                     unsigned int /*numberOfChannels*/,
                                                     int /*bitsPerSample*/,
                                                     const StringPairArray& /*metadataValues*/,
                                                     int /*qualityOptionIndex*/)
{
    jassertfalse; // not yet implemented!
    return nullptr;
}

CoreAudioFormat 的读取与写入流程与 WavAudioFormat 一致,没有其他惊喜,不再赘述。

CoreAudioFormat core_audio;

readFromAudioFormat("path_to_audio_file", &core_audio);

WindowsMediaAudioFormat

与 CoreAudioFormat 类似,WindowsMediaAudioFormat 仅在 Windows 下可用,它使用 “Windows Media codecs” 来读取文件。由于手头没有 Windows 机器,没法进行测试了。但是通过源码,以及结合上述的 AudioFormat,聪明的你肯定能够摸清它套路。

同样的,WindowsMediaAudioFormat 只支持读取,不支持写入,其 createWriterFor 实现为空

AudioFormatWriter* WindowsMediaAudioFormat::createWriterFor (OutputStream* /*streamToWriteTo*/, double /*sampleRateToUse*/,
                                                             unsigned int /*numberOfChannels*/, int /*bitsPerSample*/,
                                                             const StringPairArray& /*metadataValues*/, int /*qualityOptionIndex*/)
{
    jassertfalse; // not yet implemented!
    return nullptr;
}

总结

通过一张表格总结上述 AudioFormat

类名 支持解码 支持编码 备注
WavAudioFormat
MP3AudioFormat
LAMEEncoderAudioFormat 需配合 lame 使用
OggVorbisAudioFormat
FlacAudioFormat
AiffAudioFormat
CoreAudioFormat 仅在 OSX 和 iOS 下使用
WindowsMediaAudioFormat 仅在 Windows 下使用

你可能感兴趣的:(c++,音频处理,JUCE,c++,音频编码解码)