The purpose of this article is to provide guidelines for creating audio conversion programs using Microsoft Media Foundation.
Introduction
The following article will guide you through the process of creating a Desktop Dialog based application that can be used to convert audio files from each other. That includes:
- the "
engine
" - a class library to be used for the actual conversion from one format to another - enumerating files in a given path, along with any sub folder in it.
- user Interface and user experience
The program I developed can run with no installation, no external DLLs and not even static libraries are needed. Just build and run.
Background
Microsoft Media Foundation is a Windows based multimedia platform that gives a developer the ability to create all kind of multimedia software.
Converting Audio Files
Converting audio files, using Microsoft Media Foundation requires encoding and decoding audio streams, and that is explained in the following tutorial.
The first building block was to create our own class for such audio processing, which we called SG_Audio(Convert)
.
SG_AudioConvert::SG_AudioConvert()
{
Init();
}
SG_AudioConvert::~SG_AudioConvert()
{
Cleanup();
}
Initializing
Our Init()
function called by the constructor, does the following:
- Check whether it has already gone through our initialization. We don't want (and should not) do it more than once.
- Call HeapSetInformation() to enable certain features for our heap. Note that we are initializing a Single Thread Apartment, and you can learn more about that term in this excellent article.
- Call MFStartup() to start Windows Media Foundation.
- Set
m_bInit
to true
, to indicate initialization has been completed.
int SG_AudioConvert::Init()
{
HRESULT hr = S_OK;
if (m_bInit)
return RET_OK;
(void)HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
hr = MFStartup(MF_VERSION);
m_bInit = TRUE;
return RET_OK; }
m_bInit = FALSE;
return RET_FAIL; }
Cleaning Up
Before we proceed, let's also cover the cleanup procedure called by our class's Destructor.
int SG_AudioConvert::Cleanup()
{
MFShutdown();
CoUninitialize();
return RET_OK; }
During cleanup, we do the following:
Our Generic Audio Conversion Function
We developed int SG_AudioConvert::ConvertProc()
to carry all file conversions from any audio type supported to any other.
We pass the following arguments to it:
- p_szSrc - our source file
- p_szDst - our destination file
TargetFormat
- our target format's GUID - see "Audio Codecs" ContainerType
- our container type - see "File Containers"
The function's prototype looks like this:
int SG_AudioConvert::ConvertProc(
const wchar_t* p_szSrc,
const wchar_t* p_szDst,
const GUID TargetFormat,
const GUID ContainerType);
The Conversion
Our generic conversion function, ConvertProc()
, looks like this:
Notes: WriteLogFile()
is one of my old logging functions described in this article.
int SG_AudioConvert::ConvertProc(const wchar_t* p_szSrc, const wchar_t* p_szDst,
const GUID TargetFormat, const GUID ContainerType)
{
CTranscoder transcoder;
HRESULT hr = S_OK;
hr = transcoder.OpenFile(p_szSrc);
if (SUCCEEDED(hr))
{
hr = transcoder.ConfigureAudioOutput(TargetFormat);
}
else
{
return RET_INPUT_FAIL; }
if (SUCCEEDED(hr))
{
hr = transcoder.ConfigureContainer(ContainerType);
}
if (SUCCEEDED(hr))
{
hr = transcoder.EncodeToFile(p_szDst);
}
if (SUCCEEDED(hr))
{
WriteLogFile(L"Output file created: %s\n", p_szDst);
}
else
{
WriteLogFile(L"Output file was not created due to error: %s\n", p_szDst);
}
if (!SUCCEEDED(hr))
{
return RET_ENC_FAIL; }
return RET_OK; }
Our Conversion Functions
Here are our conversion functions:
The following six functions cover conversions among every combination of the following audio formats: .mp3, .wav and .m4a.
int SG_AudioConvert::Wav_to_Mp3(const wchar_t* p_szWavFile, const wchar_t* p_szMp3File)
{
if (!m_bInit)
return RET_NOT_INIT;
return(ConvertProc(p_szWavFile, p_szMp3File,
MFAudioFormat_MP3, MFTranscodeContainerType_MP3));
}
int SG_AudioConvert::M4A_to_Mp3(const wchar_t* p_szM4AFile, const wchar_t* p_szMp3File)
{
if (!m_bInit)
return RET_NOT_INIT;
return(ConvertProc(p_szM4AFile, p_szMp3File,
MFAudioFormat_MP3, MFTranscodeContainerType_MP3));
}
int SG_AudioConvert::Wav_to_M4A(const wchar_t* p_szWavFile, const wchar_t* p_szM4AFile)
{
if (!m_bInit)
return RET_NOT_INIT;
return(ConvertProc(p_szWavFile, p_szM4AFile, MFAudioFormat_AAC,
MFTranscodeContainerType_MPEG4));
}
int SG_AudioConvert::MP3_to_M4A(const wchar_t* p_szMp3File, const wchar_t* p_szM4AFile)
{
if (!m_bInit)
return RET_NOT_INIT;
return(ConvertProc(p_szMp3File, p_szM4AFile,MFAudioFormat_AAC,
MFTranscodeContainerType_MPEG4));
}
int SG_AudioConvert::MP3_to_Wav(const wchar_t* p_szMp3File, const wchar_t* p_szWavFile)
{
if (!m_bInit)
return RET_NOT_INIT;
return(ConvertProc(p_szMp3File, p_szWavFile, MFAudioFormat_PCM,
MFTranscodeContainerType_WAVE));
}
int SG_AudioConvert::M4A_to_Wav(const wchar_t* p_szM4AFile, const wchar_t* p_szWavFile)
{
if (!m_bInit)
return RET_NOT_INIT;
return(ConvertProc(p_szM4AFile, p_szWavFile, MFAudioFormat_PCM,
MFTranscodeContainerType_WAVE));
}
Our File Search Mechanism
I have recently updated some old code (thanks to Louka Diagnekov) to support modern applications, including UNICODE strings and it is hosted in this repository.
One of the nice features of this class is the ability to set multiple queries in one search. We can also recursively scan folders and their subfolders for files matching our creteria.
Here is the main struct
filled before running a search. Note that this class is a bit slow when it comes to millions of files, but for the purpose of our audio converter tool, it works fine.
struct FindFileOptions_t
{
bool recursive; bool returnFolders;
bool *terminateValue;
wstring location;
wstring filter;
wstring excludeFile; wstring excludeDir; };
Our Operation Modes
We have defined nine Operation Modes to covert conversion between one or two formats into the third one. That way, we can either search for one or two types of files within a given path and when found, convert to the third format.
typedef enum
{
M4A_WAV_TO_MP3 = 0, MP3_M4A_TO_WAV = 1, MP3_WAV_TO_M4A = 2, M4A_TO_MP3 = 3, WAV_TO_MP3 = 4, MP3_TO_WAV = 5, M4A_TO_WAV = 6, WAV_TO_M4A = 7, MP3_TO_M4A = 8,
LAST_ELEMENT = 9
} OperationMode;
Let's take one Operation Mode and explain it in details. For example, MP3_WAV_TO_M4A
.
During this mode, we would want to search in a given path for both .mp3 and .wav files and convert all found files into m4a.
When this mode is selected, we do the following:
Search for files with the following query:
#define QUERY_MP3_WAV L"*.mp3;*.wav";
So going back to our FindFile
class, we set it like that:
opts.filter = QUERY_MP3_WAV;
We then call:
scanPath(wstring path)
to start our file search. When the search is completed, we have an array with all found files which are then converted to our target audio type.
The User Interface
The software is based on a Dialog based MFC application. The Dialog is resizable and each element is adjusted whenever the Dialog is resized. That is achieved using some very old (but rock solid) code from the following article by Marc Richarme. Also the Dialog has its own skin, background color, transparent element so it looks better than the standard MFC application.
History
- 16th August, 2020: Initial version