Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop

Creating a Desktop Application for Converting Audio Files from Each Other

5.00/5 (7 votes)
16 Aug 2020CPOL3 min read 15.2K   377  
Desktop application that can be used to convert from and to various audio formats, such as .mp3, .m4a and .wav
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).

C++
SG_AudioConvert::SG_AudioConvert()
{
    // Initialize whatever needs to be initialized
    Init();
}

SG_AudioConvert::~SG_AudioConvert()
{
    // Clean up whatever needs to be cleaned up
    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.
C++
int SG_AudioConvert::Init()
{
    HRESULT hr = S_OK;

    // check already initialized
    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; // success
    }

    m_bInit = FALSE;
    return RET_FAIL; // fail
}

Cleaning Up

Before we proceed, let's also cover the cleanup procedure called by our class's Destructor.

C++
int SG_AudioConvert::Cleanup()
{
    MFShutdown();
    CoUninitialize();

    return RET_OK; // success
}

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:

C++
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.

C++
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;

    // Create a media source for the input file.
    hr = transcoder.OpenFile(p_szSrc);
    if (SUCCEEDED(hr))
    {
        //Configure the profile and build a topology.
        hr = transcoder.ConfigureAudioOutput(TargetFormat);
    }
    else
    {
        return RET_INPUT_FAIL; // open input file fail
    }

    if (SUCCEEDED(hr))
    {
        hr = transcoder.ConfigureContainer(ContainerType);
    }

    //Transcode and generate the output file.
    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; // encoding failed
    }

    return RET_OK;           // encoding success
}

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.

C++
// Convert to MP3
int SG_AudioConvert::Wav_to_Mp3(const wchar_t* p_szWavFile, const wchar_t* p_szMp3File)
{
    // check initialize
    if (!m_bInit)
        return RET_NOT_INIT;

    // convert
    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)
{
    // check initialize
    if (!m_bInit)
        return RET_NOT_INIT;

    // Convert
    return(ConvertProc(p_szM4AFile, p_szMp3File, 
           MFAudioFormat_MP3, MFTranscodeContainerType_MP3));
}

// Convert to M4A
int SG_AudioConvert::Wav_to_M4A(const wchar_t* p_szWavFile, const wchar_t* p_szM4AFile)
{
    // check initialize
    if (!m_bInit)
        return RET_NOT_INIT;

    // Convert
    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)
{
    // check initialize
    if (!m_bInit)
        return RET_NOT_INIT;

    // Convert
    return(ConvertProc(p_szMp3File, p_szM4AFile,MFAudioFormat_AAC, 
           MFTranscodeContainerType_MPEG4));
}

// Convert to Wav
int SG_AudioConvert::MP3_to_Wav(const wchar_t* p_szMp3File, const wchar_t* p_szWavFile)
{
    // check initialize
    if (!m_bInit)
        return RET_NOT_INIT;

    // Convert
    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)
{
    // check initialize
    if (!m_bInit)
        return RET_NOT_INIT;

    // Convert
    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.

C++
// Specifies settings to use for searching for files
struct FindFileOptions_t
{
    bool recursive;          // Whether to look inside subdirectories
    bool returnFolders;      // Return folder names as results too

    bool *terminateValue;    // Value to check to see whether search should be
                             // terminated

    wstring location;        // Where to search for files

    wstring filter;          // Filter for files to be included

    wstring excludeFile;     // Exclude filter for files
    wstring excludeDir;      // Exclude filter for directories
};

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.

C++
typedef enum
{
    M4A_WAV_TO_MP3 = 0,        // convert m4a and wav to mp3
    MP3_M4A_TO_WAV = 1,        // convert mp3 and m4a to wav
    MP3_WAV_TO_M4A = 2,        // convert mp3 and wav to m4a
    M4A_TO_MP3 = 3,            // convert m4a to mp3
    WAV_TO_MP3 = 4,            // convert wav to mp3
    MP3_TO_WAV = 5,            // convert mp3 to wav
    M4A_TO_WAV = 6,            // convert m4a to wav
    WAV_TO_M4A = 7,            // convert wav to m4a
    MP3_TO_M4A = 8,            // convert mp3 to m4a

    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:

C++
#define QUERY_MP3_WAV    L"*.mp3;*.wav";

So going back to our FindFile class, we set it like that:

C++
opts.filter = QUERY_MP3_WAV;

We then call:

C++
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

Image 1

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)