创建用于相互转换音频文件的桌面应用程序





5.00/5 (7投票s)
可用于在各种音频格式之间进行转换的桌面应用程序,例如 .mp3、.m4a 和 .wav
引言
以下文章将指导您完成创建基于对话框的桌面应用程序的过程,该应用程序可用于相互转换音频文件。这包括
- “
engine
” - 用于将一种格式实际转换为另一种格式的类库 - 枚举给定路径中的文件,以及其中的任何子文件夹。
- 用户界面和用户体验
我开发的程序无需安装即可运行,不需要外部 DLL 甚至静态库。只需构建并运行即可。
背景
Microsoft Media Foundation 是一个基于 Windows 的多媒体平台,它使开发人员能够创建各种多媒体软件。
转换音频文件
使用 Microsoft Media Foundation 转换音频文件需要编码和解码音频流,这在以下教程中进行了说明。
第一个构建块是创建我们自己的类来进行此类音频处理,我们称之为 SG_Audio(Convert)
。
SG_AudioConvert::SG_AudioConvert()
{
// Initialize whatever needs to be initialized
Init();
}
SG_AudioConvert::~SG_AudioConvert()
{
// Clean up whatever needs to be cleaned up
Cleanup();
}
初始化
我们的构造函数调用的 Init()
函数执行以下操作
- 检查它是否已经完成了我们的初始化。我们不希望(也不应该)执行多次。
- 调用 HeapSetInformation() 来为我们的堆启用特定功能。请注意,我们正在初始化一个单线程单元 (Single Thread Apartment),您可以在这篇优秀的文章中了解有关该术语的更多信息。
- 调用 MFStartup() 来启动 Windows Media Foundation。
- 将
m_bInit
设置为true
,以表明初始化已完成。
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
}
清理
在我们继续之前,我们还应介绍我们的类的析构函数调用的清理过程。
int SG_AudioConvert::Cleanup()
{
MFShutdown();
CoUninitialize();
return RET_OK; // success
}
在清理过程中,我们执行以下操作
- 调用 MFShutdown()
- 调用 CoUninitialize()
我们的通用音频转换函数
我们开发了 int SG_AudioConvert::ConvertProc()
来处理所有文件转换,从任何支持的音频类型到任何其他类型。
我们将以下参数传递给它
- p_szSrc - 我们的源文件
- p_szDst - 我们的目标文件
TargetFormat
- 我们的目标格式的 GUID - 请参阅“音频编解码器”ContainerType
- 我们的容器类型 - 请参阅“文件容器”
该函数的原型如下所示
int SG_AudioConvert::ConvertProc(
const wchar_t* p_szSrc,
const wchar_t* p_szDst,
const GUID TargetFormat,
const GUID ContainerType);
转换
我们的通用转换函数 ConvertProc()
如下所示
注意: WriteLogFile()
是我在这篇文章中描述的我的一个旧日志函数。
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
}
我们的转换函数
这是我们的转换函数
以下六个函数涵盖了.mp3、.wav 和 .m4a 之间每种组合的转换。
// 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));
}
我们的文件搜索机制
我最近更新了一些旧代码(感谢 Louka Diagnekov)以支持现代应用程序,包括 UNICODE 字符串,它托管在此存储库中。
此类的出色功能之一是能够在一个搜索中设置多个查询。我们还可以递归地扫描文件夹及其子文件夹以查找符合我们条件的任何文件。
这是在运行搜索之前填充的主 struct
。请注意,此类在处理数百万个文件时速度有点慢,但对于我们的音频转换器工具而言,它运行良好。
// 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
};
我们的操作模式
我们定义了九种操作模式,用于将一种或两种格式之间的转换转换为第三种格式。这样,我们可以搜索给定路径中的一种或两种类型的文件,找到后,将其转换为第三种格式。
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;
让我们以一种操作模式为例进行详细说明。例如,MP3_WAV_TO_M4A
。
在此模式下,我们希望在给定路径中搜索.mp3 和 .wav 文件,并将所有找到的文件转换为 m4a。
当选择此模式时,我们执行以下操作
使用以下查询搜索文件
#define QUERY_MP3_WAV L"*.mp3;*.wav";
因此,回到我们的 FindFile
类,我们这样设置它
opts.filter = QUERY_MP3_WAV;
然后我们调用
scanPath(wstring path)
来启动我们的文件搜索。搜索完成后,我们得到一个包含所有找到文件的数组,然后这些文件被转换为我们的目标音频类型。
用户界面
该软件基于基于对话框的 MFC 应用程序。对话框可调整大小,并且每当对话框调整大小时,每个元素都会随之调整。这是通过以下文章中 Marc Richarme 的一些非常老的(但非常稳定)代码实现的。此外,对话框还拥有自己的皮肤、背景颜色、透明元素,因此比标准 MFC 应用程序看起来更好。
历史
- 2020 年 8 月 16 日:初始版本