In the last post we looked at how to play an audio clip from a file.
This time around, we're going to use the opus library to compress audio and then decompress it, and see the effects it has on size.
This post was supposed to come last month, but lots of little details came along the way and I couldn't keep it as straightforward as I wanted.
Instead, I give you the second step in a new opusfun
repo.
opus
is available as a library, so we'll need to figure out how to deal with dependencies.
We could use any number of solutions like vcpkg
, but for our pet project we're going to do this with more primitive tools.
We'll simply write all the steps we care for in a build.ps1 PowerShell file.
We're going to do the following.
opus
to encode/decode, and we'll grab a helper from opus-tools
to resample (more on this later).opus
as a static library we can reference from our project.There are actually three libraries that we might find interesting.
First let's get the sources at the v1.5.2 tag with git clone https://github.com/xiph/opus && cd opus && git checkout v1.5.2
Next we can setup the CMake build with mkdir build && cd build && cmake .. -G "Visual Studio 17 2022"
And finally, build it with cmake --build .
Note that by default we're going to be building a debug project - this will come back later.
Once the process is complete, you'll find a static library at Debug\opus.lib
under your build directory.
In play.cpp
you will see the following main function, which shows the flow of what we'll be doing - compressing a data file, decompressing it back, then playing it to make sure we didn't mess anything up. File names and such that would normally be parameters are simply globals, to keep this more streamlined.
int main()
{
HRESULT hr = S_OK;
IFC(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
IFC(RunFileCompress());
IFC(RunFileDecompress());
IFC(RunFilePlayback());
CoUninitialize();
Cleanup:
return FAILED(hr) ? 1 : 0;
}
Let's look at compression first. Because we'll eventually want to use this over the network, we'll be using the encoder directly, so basically following the encoder documentation. This is implemented in the RunFileCompress
function, and has the following flow.
DirectX::LoadWAVAudioFromFileEx
.opuslib
only works with specific sample rates, and our sample file happens to not be one of them. Instead we're going to use resample the original audio into a supported sample rate. In ResampleWaveData
, you can see how we use speex_resampler_process_float
(or _int
) to do the data conversion.opus_encoder_create
, and then iterate in frame chunks using opus_encode_float
(or _int
, again) to turn the pulse code modulation (PCM) representation into opus representation.As we go along, we write out a file with the sample rate, and then a sequence of packet lengths followed by packet data. This is just something I made up for this sample, instead of using a better-supported format.
Next up, let's look at decompressing our file and playing it back. We're decompressing to a file rather than on-the-fly, so we don't have to deal with any sort of timing, and the code is again straightforward.
opus_decode
to turn them into PCM values.To write out the file, I made some modifications to WAVFileReader
, adding a WriteWAVDataToFile
function. The implementation file had all the declarations I needed, so it was easiest to simply append this there. The github file has you covered.
Same as the last post where we looked at how to play an audio clip from a file.
The compilation is done in a single command-line invocation, and it's worth breaking it down. cl.exe /Iopus\include /EHsc /MDd /D "RANDOM_PREFIX=opustools" /D "OUTSIDE_SPEEX" /D "RESAMPLE_FULL_SINC_TABLE" play.cpp WAVFileReader.cpp resample.c /Fe: play.exe /link ole32.lib user32.lib /LIBPATH:opus\build\Debug
is a bit too much.
First, note that /link
is separating the compiler options that come before from the linker options that come after it.
/Iopus\include
includes the opus\include directory in the header search path, so we can find the opus header and it can find its related headers./EHsc
sets up exception handling, which we need because we're using std::vector
now./MDd
uses the multithreaded debug CRT library, which we need to line up with how we built the opus library via cmake./D ...
a few defines that the speex resampling libary uses/LIBPATH:opus\build\Debug
tells the linker where to find the opus library.We could have added opus.lib
to the linker flags, but I already had added it via #pragma comment(lib, "opus.lib")
when I first included it, so there was no need for that.
Note that what we've built is basically a baby version of some of the functionality available in the Opus tools themselves.
Next time, we'll take this show on the road and use this to transmit data.
Happy audio compression!
Tags: audio cmake cpp powershell