软件设备
Visual Studio2022
实验内容
1.设计一个程序,利用三个Media Session同时播放三个音频文件。
2.打开两个音频文件(限Wav文件,并具有相同的采样频率和量化深度),然后用第二个音频文件左声道代替第一个音频文件左声道,但保留第一个音频文件的右声道,最后播放第一个音频文件,并观察结果。
3.设计实现静音效果。
实验前置内容
如何用VS2022创建一个WIN32项目
创建项目->选择windows桌面应用程序即可,里面会有一些示例代码,项目是可以直接运行的(直接无脑下一步就行)。
如何获取免费的音频
老师的代码中只提供了TheClassic.wav文件,而我们的实验中是需要更多的wav文件的,这里我是去https://www.ear0.com这个网站中下载的,建议最好下载wav格式的文件,因为mp3可能会出现一些问题。
如何让你的程序播放音频
有两种方式,一种是使用playSound组件,一种是使用MediaSession组件,实验用的是第二种,我们都介绍一下。你也可以按照我的步骤去添加一遍代码,因为我的项目中包含了这两种方式。
playSound组件播放音频的步骤
-
打开资源视图,我们在菜单栏里添加两个按钮,分别为播放和暂停;步骤如下:
-
然后重新设计menu,添加两个按钮:
-
双击按钮,在右下角的栏目里修改一下ID:
-
然后我们回到Resource.h文件中,删除掉刚刚被我们替换的ID,保留刚刚更改的ID:
-
回到.cpp文件,首先在头文件中添加MMsystem.h文件:
-
然后我们在主窗口函数中添加事件,使用PlaySound函数来控制音乐的播放与暂停(这里被注释的就是我们需要添加的,其他的先不用管,那是第二种方法要用到的代码):
-
将音频文件放入到项目目录中:
-
接下来直接编译是会出错的,我们还需要添加链接文件,步骤是点击项目->属性->链接器->附加依赖项,然后添加库:
-
OK!现在你可以编译运行你的代码,然后播放进行测试:
MediaSession组件播放音频的步骤
-
在刚刚的项目中,我们直接修改一下framework.h,添加最后两个头文件:
-
然后我们再添加一些依赖项(参考playSound中的添加步骤)
mfplat.lib mfreadwrite.lib mfuuid.lib propsys.lib shell32.lib d2d1.lib winmm.lib mf.lib //这是本次实验中需要用到的所有依赖项,你可以直接全部引入进去。针对这个步骤中的依赖项,你可以选择看PPT或者教材中的步骤。
-
接下来,回到我们的.cpp文件中,在添加全局变量的区域中去创建我们待会需要用到的全局变量:
IMFMediaSession* m_pSession;
IMFSourceResolver* m_pSourceResolver = NULL;
IUnknown* pSource = NULL;
IMFMediaSource* m_pSource;
IMFTopology* m_pTopology = NULL;
IMFPresentationDescriptor* m_pSourcePD = NULL;
DWORD cSourceStreams = 0;
IMFTopologyNode* pSourceNode = NULL;
IMFTopologyNode* pOutputNode = NULL;
IMFActivate* pSinkActivate = NULL;
IMFStreamDescriptor* pSD = NULL;
IMFMediaTypeHandler* pHandler = NULL;
IMFActivate* pActivate = NULL;
PROPVARIANT var;
-
创建媒体会话函数,我们可以将这个函数放在代码的最下面:
void CreateMediaSession(HWND hWnd)
{
HRESULT hr;
// 初始化Media Foundation平台
hr = MFStartup(MF_VERSION);
// 创建媒体会话
hr = MFCreateMediaSession(NULL, &m_pSession);
// 创建源解析器
hr = MFCreateSourceResolver(&m_pSourceResolver);
MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID;
// 利用源解析器创建媒体源
hr = m_pSourceResolver->CreateObjectFromURL(
L"TheClassic.wav", // URL of the source.
MF_RESOLUTION_MEDIASOURCE, // Create a source object.
NULL, // Optional property store.
&ObjectType, // Receives the created object type.
&pSource // Receives a pointer to the media source.
);
hr = pSource->QueryInterface(IID_PPV_ARGS(&m_pSource));
// Create the presentation descriptor for the media source.
hr = m_pSource->CreatePresentationDescriptor(&m_pSourcePD);
// Create a new topology.
hr = MFCreateTopology(&m_pTopology);
// Get the number of streams in the media source.
hr = m_pSourcePD->GetStreamDescriptorCount(&cSourceStreams);
// For each stream, create the topology nodes and add them to the topology.
for (DWORD i = 0; i < cSourceStreams; i++)
{
BOOL fSelected = FALSE;
hr = m_pSourcePD->GetStreamDescriptorByIndex(i, &fSelected, &pSD);
hr = pSD->GetMediaTypeHandler(&pHandler);
GUID guidMajorType;
hr = pHandler->GetMajorType(&guidMajorType);
if (MFMediaType_Audio == guidMajorType)
{
// Create the audio renderer.
hr = MFCreateAudioRendererActivate(&pActivate);
}
else if (MFMediaType_Video == guidMajorType)
{
// Create the video renderer.
hr = MFCreateVideoRendererActivate(hWnd, &pActivate);
}
// 创建拓扑的输入节点(即源节点)
hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &pSourceNode);
hr = pSourceNode->SetUnknown(MF_TOPONODE_SOURCE, m_pSource);
hr = pSourceNode->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR,
m_pSourcePD);
hr = pSourceNode->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, pSD);
hr = m_pTopology->AddNode(pSourceNode);
// 创建拓扑的输出节点
hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &pOutputNode);
hr = pOutputNode->SetObject(pActivate);
hr = pOutputNode->SetUINT32(MF_TOPONODE_STREAMID, 0);
hr = pOutputNode->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE);
hr = m_pTopology->AddNode(pOutputNode);
// Connect the source node to the output node.
hr = pSourceNode->ConnectOutput(0, pOutputNode, 0);
}
// 将拓扑设置给Media Session
m_pSession->SetTopology(0, m_pTopology);
}
-
添加前向声明(刚刚添加的函数):
void DrawBackground(HWND hWnd);
-
然后我们在winMain主函数中引入刚刚创建的函数:
-
最后,我们重新写一下在playSound方法中写过的按钮事件即可,(到此,你可以编译运行一下代码,是一样的效果):
到此,前置内容结束,接下来是我们的实验部分。
利用三个Media Session同时播放三个音频文件
这个其实比较简单,我们只需要重复刚刚的步骤三次即可。
-
在已经实现MediaSession组件播放音频的基础上,我们定义三个Media Session(代码没给全,你可以自己添加一下):
-
然后我们可以编写几乎相同的三个CreateMediaSession函数(其中,只有媒体源的URL是不一样的,媒体源需要你下载好并放到同一个目录中去,具体可以参考如何获取免费的音频)
-
接下来,我们继续在winMain主函数中引入刚刚创建的函数,然后再重写一下按钮事件,这时可以启动三个mediaSession看看,此时应该能听到三个音乐的声音,到此,实验一的第一个内容完成了!:
声道替换
-
第一个步骤,需要你在完成刚刚的实验的基础上,按照教材的步骤,去编写转码内容,完成音频转码程序的部分(教材给的很详细,直接按步骤来就可以了,其中的代码你也可以在老师所给的软件资源中的AudioMix中查看)
代码可以参考这个项目:
-
这里我是直接按照教材,将AudioMix的代码复制到我的项目中的,我只写下需要修改的地方,首先是这个地方我们播放out1.wav即可,这里的按钮事件是我自己添加的按钮,你也可以添加你自己的按钮来控制声道替换的播放事件:
-
其次是这个文件名需要修改,请修改为你自己的音频文件,否则项目是运行不成功的:
-
然后我们就可以运行一下我们的项目了,这个时候应该是可以按照教材中步骤运行成功的,带耳机的话效果会很好,左耳进左声道,右耳右声道:
-
但是!教材中的代码跟实验内容还是有点不同的,我们需要重写下WriteWaveData函数,我们只需要知道,这个是实现我们的代码的核心函数即可,具体的引用关系,你也可以再看看教材的介绍。以下是我们重写的代码。
其中,被注释掉的地方就是教材的代码,没被注释的就是我们重写的代码。至此,实验二的部分就完成了,您可以直接运行代码,然后播放out1.wav即可,关于播放的形式您可以选择在菜单中新建一个按钮点击播放。
静音效果
不知道聪明的你是否发现了,其实在声道替换的最后,我们已经实现了静音效果,实际上,您只需播放out2.wav即可,这个就是一个静音文件。其实pAudioData控制的就是out1.wav中的数据流,pAudioDataAnother控制的就是out2.wav中的数据流。你也可以自己在菜单中添加一个按钮,然后写一个事件来控制out2.wav的播放与暂停。
写在后面
播放的时候我们可以设置自己的背景,代码可以参考我的,也可以参考老师的软件资源提供的,下面是我的代码:
//我重写了下加载图片的方式
void DrawBackground(HWND hwnd) {
// 加载图片
HBITMAP hBitmap = (HBITMAP)LoadImage(NULL, L"groundimage2.bmp", IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!hBitmap) {
// 加载图片失败,可以处理错误
return;
}
// 获取窗口设备上下文
HDC hdcWindow = GetDC(hwnd);
// 创建与窗口设备上下文兼容的内存设备上下文
HDC hdcMem = CreateCompatibleDC(hdcWindow);
// 选择位图到内存设备上下文
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hdcMem, hBitmap);
// 获取窗口的客户区大小
RECT rect;
GetClientRect(hwnd, &rect);
// 将内存设备上下文的内容复制到窗口设备上下文
StretchBlt(hdcWindow, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, rect.right, rect.bottom, SRCCOPY);
// 恢复旧位图
SelectObject(hdcMem, hOldBitmap);
// 清理资源
ReleaseDC(hwnd, hdcWindow);
DeleteDC(hdcMem);
DeleteObject(hBitmap);
}
将这个函数放在这个重绘事件下即可:
当然,你可能还需要对这个函数进行前置的声明。
最终的效果就是这样的:
参考文章:
https://www.codenong.com/cs106759209/#google_vignette
CSDN的内容可能有些格式上的问题,更多内容可以查看:https://cds007.github.io/
有问题可在评论区交流~