全新超详细!2024 CSU 多媒体技术与应用实验一(详细版)

软件设备

Visual Studio2022

实验内容

1.设计一个程序,利用三个Media Session同时播放三个音频文件。

2.打开两个音频文件(限Wav文件,并具有相同的采样频率和量化深度),然后用第二个音频文件左声道代替第一个音频文件左声道,但保留第一个音频文件的右声道,最后播放第一个音频文件,并观察结果。

3.设计实现静音效果。

实验前置内容

如何用VS2022创建一个WIN32项目

创建项目->选择windows桌面应用程序即可,里面会有一些示例代码,项目是可以直接运行的(直接无脑下一步就行)。

如何获取免费的音频

老师的代码中只提供了TheClassic.wav文件,而我们的实验中是需要更多的wav文件的,这里我是去https://www.ear0.com这个网站中下载的,建议最好下载wav格式的文件,因为mp3可能会出现一些问题。

如何让你的程序播放音频

有两种方式,一种是使用playSound组件,一种是使用MediaSession组件,实验用的是第二种,我们都介绍一下。你也可以按照我的步骤去添加一遍代码,因为我的项目中包含了这两种方式。

playSound组件播放音频的步骤
  1. 打开资源视图,我们在菜单栏里添加两个按钮,分别为播放和暂停;步骤如下:

  2. 然后重新设计menu,添加两个按钮:

  3. 双击按钮,在右下角的栏目里修改一下ID:

  4. 然后我们回到Resource.h文件中,删除掉刚刚被我们替换的ID,保留刚刚更改的ID:

  5. 回到.cpp文件,首先在头文件中添加MMsystem.h文件:

  6. 然后我们在主窗口函数中添加事件,使用PlaySound函数来控制音乐的播放与暂停(这里被注释的就是我们需要添加的,其他的先不用管,那是第二种方法要用到的代码):

  7. 将音频文件放入到项目目录中:

  8. 接下来直接编译是会出错的,我们还需要添加链接文件,步骤是点击项目->属性->链接器->附加依赖项,然后添加库:

  9. OK!现在你可以编译运行你的代码,然后播放进行测试:

MediaSession组件播放音频的步骤
  1. 在刚刚的项目中,我们直接修改一下framework.h,添加最后两个头文件:

  2. 然后我们再添加一些依赖项(参考playSound中的添加步骤)

    mfplat.lib
    mfreadwrite.lib
    mfuuid.lib
    propsys.lib
    shell32.lib
    d2d1.lib
    winmm.lib
    mf.lib
    //这是本次实验中需要用到的所有依赖项,你可以直接全部引入进去。针对这个步骤中的依赖项,你可以选择看PPT或者教材中的步骤。
  3. 接下来,回到我们的.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;
  1. 创建媒体会话函数,我们可以将这个函数放在代码的最下面:

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);
}
  1. 添加前向声明(刚刚添加的函数):

    void DrawBackground(HWND hWnd);
  1. 然后我们在winMain主函数中引入刚刚创建的函数:

  2. 最后,我们重新写一下在playSound方法中写过的按钮事件即可,(到此,你可以编译运行一下代码,是一样的效果):

到此,前置内容结束,接下来是我们的实验部分。

利用三个Media Session同时播放三个音频文件

这个其实比较简单,我们只需要重复刚刚的步骤三次即可。

  1. 在已经实现MediaSession组件播放音频的基础上,我们定义三个Media Session(代码没给全,你可以自己添加一下):

  2. 然后我们可以编写几乎相同的三个CreateMediaSession函数(其中,只有媒体源的URL是不一样的,媒体源需要你下载好并放到同一个目录中去,具体可以参考如何获取免费的音频)

  1. 接下来,我们继续在winMain主函数中引入刚刚创建的函数,然后再重写一下按钮事件,这时可以启动三个mediaSession看看,此时应该能听到三个音乐的声音,到此,实验一的第一个内容完成了!:

声道替换
  1. 第一个步骤,需要你在完成刚刚的实验的基础上,按照教材的步骤,去编写转码内容,完成音频转码程序的部分(教材给的很详细,直接按步骤来就可以了,其中的代码你也可以在老师所给的软件资源中的AudioMix中查看)

代码可以参考这个项目:

  1. 这里我是直接按照教材,将AudioMix的代码复制到我的项目中的,我只写下需要修改的地方,首先是这个地方我们播放out1.wav即可,这里的按钮事件是我自己添加的按钮,你也可以添加你自己的按钮来控制声道替换的播放事件:

  1. 其次是这个文件名需要修改,请修改为你自己的音频文件,否则项目是运行不成功的:

  1. 然后我们就可以运行一下我们的项目了,这个时候应该是可以按照教材中步骤运行成功的,带耳机的话效果会很好,左耳进左声道,右耳右声道:

  1. 但是!教材中的代码跟实验内容还是有点不同的,我们需要重写下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/

有问题可在评论区交流~

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值