[{"data":1,"prerenderedAt":272},["ShallowReactive",2],{"page-FMod-1":3,"page-count-FMod":103},[4,134],{"id":5,"title":6,"body":7,"date":109,"description":13,"extension":110,"meta":111,"navigation":114,"path":126,"seo":127,"stem":128,"tags":129,"__hash__":133},"blogs\u002F_legacy\u002F2015\u002F2015-08-19-ue4-fmod-usage-in-cpp.md","UE4中C++模式FMod的使用",{"type":8,"value":9,"toc":102},"minimark",[10,14,17,20,24,27,30,41,44,50,53,56,60,63,69,72,75,78,84,87,93,96],[11,12,13],"p",{},"FMod在为UE4提供音效便利的同时，有提供底层的API来方便更加详尽的需求。",[11,15,16],{},"当前UE4版本4.9.2；FMod版本1.07.00。",[11,18,19],{},"目前版本下对FMod的使用和之前进行UE4与FMod音乐可视化验证的时候并没有什么不同，这里主要是总结下使用中遇到的问题。",[21,22,23],"h2",{"id":23},"编码问题",[11,25,26],{},"编码问题主要出在中文路径的处理上，UE4内部使用的是UTF-16编码。",[11,28,29],{},"因此，将通过Save&Load之类保存起来的路径提供给FMod使用的时候，需要对字符编码进行转换。",[31,32,37],"pre",{"className":33,"code":35,"language":36},[34],"language-text","\u002F\u002F 传入参数：const FString& FileFullPath\n\nstd::stringstream dts;\ndts \u003C\u003C TCHAR_TO_UTF8(*FileFullPath);\nresult = system->createStream(dts.str().c_str(), FMOD_LOOP_NORMAL | FMOD_2D, 0, &sound);\n","text",[38,39,35],"code",{"__ignoreMap":40},"",[11,42,43],{},"关于字符编码，在使用Tag系统的时候还是会遇到。",[31,45,48],{"className":46,"code":47,"language":36},[34],"int NumTags;\nresult = sound_to_analyse->getNumTags(&NumTags, NULL);\nif (result != FMOD_RESULT::FMOD_OK) break;\nif (NumTags \u003C= 0) break;\nFMOD_TAG MusicTag;\nfor (int mTag = 0; mTag \u003C NumTags; ++mTag){ sound_to_analyse->getTag(NULL, mTag, &MusicTag);\nstd::stringstream ts;\nts \u003C\u003C \"Tag:\";\nts \u003C\u003C MusicTag.name;\nts \u003C\u003C \" Type:\";\nts \u003C\u003C MusicTag.type;\nts \u003C\u003C \" Data:\";\nts \u003C\u003C std::string((char*)MusicTag.data);\nts \u003C\u003C \" DataLength:\";\nts \u003C\u003C MusicTag.datalen;\nts \u003C\u003C \"\\n\"; OutputDebugStringA(ts.str().c_str()); } result = sound_to_analyse->getTag(\"TITLE\", 0, &MusicTag);\n",[38,49,47],{"__ignoreMap":40},[11,51,52],{},"这里目前似乎是有BUG存在的，通过getTag对音乐的标签进行读取时，如果标签是非英文字符的话，读取出来的字符编码无法被正常的解析。并不是读写方式的问题，通过断点直接查看内存的话会看到其中的编码不符合文档中所描述的编码格式。",[11,54,55],{},"因此建议音乐的预览图片使用FMod来读取APIC的tag，而其他的tag使用别的库进行读取，例如使用TagLib。",[21,57,59],{"id":58},"apic图片读取","APIC图片读取",[11,61,62],{},"音乐文件的预览图片信息一般存储于APIC标签中，要将读取出的图片数据展示到界面上。可以借助ImageWrapper和SImage来实现。",[31,64,67],{"className":65,"code":66,"language":36},[34],"\u002F\u002F 变量定义\nTSharedPtr ImageTitle;\nIImageWrapperPtr ImageWrapper;\nUTexture2D* mImageHolder;\nFSlateDynamicImageBrush* mSlateDyn;\n\u002F\u002F 实际读取\nresult = sound_to_analyse->getTag(\"APIC\", 0, &MusicTag);\nif (result != FMOD_RESULT::FMOD_OK) break;\nTArray RawFloatImgData;\nRawFloatImgData.AddUninitialized(MusicTag.datalen);\nFMemory::Memcpy(RawFloatImgData.GetData(), MusicTag.data, MusicTag.datalen*sizeof(uint8));\nEImageFormat::Type MusicFormat = EImageFormat::PNG;\nchar tc = RawFloatImgData[1];\nif (tc == 'i'){\ntc = RawFloatImgData[7];\nswitch (tc)\n{\ncase 'j':\ncase 'J':\nMusicFormat = EImageFormat::JPEG;\nbreak;\ncase 'b':\ncase 'B':\nMusicFormat = EImageFormat::BMP;\nbreak;\ndefault:\nbreak;\n}\n}\nIImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName(\"ImageWrapper\"));\nImageWrapper = ImageWrapperModule.CreateImageWrapper(MusicFormat);\nif (!ImageWrapper.IsValid()) break;\nif (!ImageWrapper->SetCompressed(RawFloatImgData.GetData(), RawFloatImgData.Num())) break;\nOutputDebugStringA(\"APIC Read Success\\n\");\nconst TArray* UncompressedBGRA = NULL;\nif (!ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA)) break;\nOutputDebugStringA(\"APIC UTexture2D OK\\n\");\nmImageHolder = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8);\nvoid* TextureData = mImageHolder->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);\nFMemory::Memcpy(TextureData, UncompressedBGRA->GetData(), UncompressedBGRA->Num());\nmImageHolder->PlatformData->Mips[0].BulkData.Unlock();\nmImageHolder->UpdateResource();\nmSlateDyn = new FSlateDynamicImageBrush(mImageHolder, FVector2D(ImageWrapper->GetWidth(), ImageWrapper->GetHeight()), FName(\"tabActiveImage\"));\nImageTitle->SetImage(mSlateDyn);\n",[38,68,66],{"__ignoreMap":40},[11,70,71],{},"由于部分的变量的初始化没有复制过来，代码仅作交流用。",[21,73,74],{"id":74},"播放回调",[11,76,77],{},"如果有需要对音乐播放的状态进行追踪，例如在音乐播放完成后进入积分画面的话。使用FMod提供的回调系统是一个不错的解决方案。",[31,79,82],{"className":80,"code":81,"language":36},[34],"channel->setMode(FMOD_LOOP_OFF);\nif (result != FMOD_RESULT::FMOD_OK) break;\nresult = channel->setCallback(FModHolders::EndCallBack);\nif (result != FMOD_RESULT::FMOD_OK) break;\n",[38,83,81],{"__ignoreMap":40},[11,85,86],{},"由于设置回调时并没有指定回调类型，故而在回调函数内部对事件进行过滤",[31,88,91],{"className":89,"code":90,"language":36},[34],"FMOD_RESULT FModHolders::EndCallBack(FMOD_CHANNELCONTROL *chanControl, FMOD_CHANNELCONTROL_TYPE controlType, FMOD_CHANNELCONTROL_CALLBACK_TYPE callbackType, void *commandData1, void *commandData2)\n{\nif (controlType == FMOD_CHANNELCONTROL_TYPE::FMOD_CHANNELCONTROL_CHANNEL){\nif (callbackType == FMOD_CHANNELCONTROL_CALLBACK_TYPE::FMOD_CHANNELCONTROL_CALLBACK_END){\n\u002F\u002F 处理代码\n",[38,92,90],{"__ignoreMap":40},[11,94,95],{},"这里需要注意的是，回调函数本身的声明必须要是Static的。",[31,97,100],{"className":98,"code":99,"language":36},[34],"static FMOD_RESULT F_CALLBACK EndCallBack(FMOD_CHANNELCONTROL *chanControl, FMOD_CHANNELCONTROL_TYPE controlType, FMOD_CHANNELCONTROL_CALLBACK_TYPE callbackType, void *commandData1, void *commandData2);\n",[38,101,99],{"__ignoreMap":40},{"title":40,"searchDepth":103,"depth":104,"links":105},2,3,[106,107,108],{"id":23,"depth":103,"text":23},{"id":58,"depth":103,"text":59},{"id":74,"depth":103,"text":74},"2015-08-19","md",{"layout":112,"status":113,"published":114,"author":115,"author_login":116,"author_email":117,"author_url":118,"wordpress_id":119,"wordpress_url":120,"date_gmt":121,"excerpt":122},"post","publish",true,{"display_name":116,"login":116,"email":117,"url":118},"chaoshikari","chaoshikari@gmail.com","\u002F",1516,"\u002F\u002F?p=1516","2015-08-18 23:48:29 +0000",{"type":8,"value":123},[124],[11,125,13],{},"\u002F2015-08-19-ue4-fmod-usage-in-cpp",{"title":6,"description":13},"_legacy\u002F2015\u002F2015-08-19-ue4-fmod-usage-in-cpp",[130,131,132],"c++","UE4","FMod","x4VnHG8oYgpU4XCrWiG9_Wzrb-tAVEXhAGEkpJn3jNI",{"id":135,"title":136,"body":137,"date":257,"description":141,"extension":110,"meta":258,"navigation":114,"path":267,"seo":268,"stem":269,"tags":270,"__hash__":271},"blogs\u002F_legacy\u002F2015\u002F2015-05-21-ue4-fmod-visulization.md","UE4与FMod音乐可视化验证",{"type":8,"value":138,"toc":255},[139,142,145,148,154,157,160,163,169,172,178,181,184,190,193,199,204,207,213,216,222,225,231,234,240,243,246,252],[11,140,141],{},"制作音游的一个重要步骤，就是验证引擎的相关技术特性，以此来决定是否更换引擎或者更改设计。",[11,143,144],{},"当前UE版本4.7.6；FMod版本1.06.02；",[11,146,147],{},"由于需要播放用户提供的音乐文件，所以需要读取用户设备上的文件。在翻阅文档之后，发现可以使用PlatformFileManager读取外部文件相关信息，同时也有获取目录信息的相关接口。",[31,149,152],{"className":150,"code":151,"language":36},[34],"FString CompleteFilePath = \"H:\u002Fmusic\u002Ftest.mp3\";\nif (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*CompleteFilePath))\n{\nOutputDebugStringA(\"Error\");\nreturn 0;\n}\n\nconst int32 FileSize = FPlatformFileManager::Get().GetPlatformFile().FileSize(*CompleteFilePath);\n",[38,153,151],{"__ignoreMap":40},[11,155,156],{},"通过上面的代码，成功获得文件大小。外部文件相关验证完成。",[11,158,159],{},"接下来就是音乐播放相关的实现了，FMod在UE4上有插件实现。基本可以满足使用需求。",[11,161,162],{},"下载并将FMod插件解压到UE4目录之后，首先在项目的*.Build.cs中添加下面的代码",[31,164,167],{"className":165,"code":166,"language":36},[34],"public class mpop : ModuleRules\n{\npublic mpop(TargetInfo Target)\n{\nPublicDependencyModuleNames.AddRange(new string[] { \"Core\", \"CoreUObject\", \"Engine\", \"InputCore\" });\n\nPrivateDependencyModuleNames.AddRange(new string[] { \"FMODStudio\" });\n}\n}\n",[38,168,166],{"__ignoreMap":40},[11,170,171],{},"这样一来在需要使用到FMod地方进行文件包含就不会遇到报错了",[31,173,176],{"className":174,"code":175,"language":36},[34],"#include \"fmod_studio.hpp\"\n",[38,177,175],{"__ignoreMap":40},[11,179,180],{},"如果在编译时遇到LNK1181之类的编译错误，建议按照提示查找FMod插件中是否提供了对应的Lib文件。如果没有的话可能需要重新编译插件或者使用更早期有提供的版本。",[11,182,183],{},"使用FMod底层API的第一步是得到一个底层的API System，根据使用方式的不同有两条路径。",[11,185,186],{},[187,188,189],"strong",{},"获取插件系统",[11,191,192],{},"适合与EndPlay和BeginPlay之类的事件挂钩进行使用。不需要自主使用update，插件内部已经有更新机制了。",[31,194,197],{"className":195,"code":196,"language":36},[34],"result = IFMODStudioModule::Get().GetStudioSystem(EFMODSystemContext::Runtime)->getLowLevelSystem(&system);\n\nif (result != FMOD_RESULT::FMOD_OK) break;\n\nresult = system->getVersion(&version);\n\nif (result != FMOD_RESULT::FMOD_OK) break;\n",[38,198,196],{"__ignoreMap":40},[11,200,201],{},[187,202,203],{},"自主创建系统",[11,205,206],{},"适合配合单例模式使用，和插件建立的系统相对独立。不过依然不需要自己进行update操作。与直接获取不同的是，需要自己进行初始化操作。",[31,208,211],{"className":209,"code":210,"language":36},[34],"result = FMOD::System_Create(&system);\n\nif (result != FMOD_RESULT::FMOD_OK) break;\n\nresult = system->getVersion(&version);\n\nif (result != FMOD_RESULT::FMOD_OK) break;\n\nresult = system->init(32, FMOD_INIT_NORMAL, extradriverdata);    \u002F\u002F \u003C进行初始化\n\nif (result != FMOD_RESULT::FMOD_OK) break;\n",[38,212,210],{"__ignoreMap":40},[11,214,215],{},"系统创建之后，音乐播放使用createsound即可",[31,217,220],{"className":218,"code":219,"language":36},[34],"result = system->createSound(\"H:\u002Fmusic\u002Ftest.mp3\", FMOD_LOOP_NORMAL | FMOD_2D, 0, &sound);\nif (result != FMOD_RESULT::FMOD_OK) break;\n\nresult = sound->getNumSubSounds(&numsubsounds);\nif (result != FMOD_RESULT::FMOD_OK) break;\n\nif (numsubsounds)\n{\nsound->getSubSound(0, &sound_to_play);\nif (result != FMOD_RESULT::FMOD_OK) break;\n}\nelse\n{\nsound_to_play = sound;\n}\n\nresult = system->playSound(sound_to_play, 0, false, &channel);\nif (result != FMOD_RESULT::FMOD_OK) break;\n",[38,221,219],{"__ignoreMap":40},[11,223,224],{},"要对音乐进行可视化，最基础的数据获取方式是对音乐进行快速傅里叶变换（FFT）。FMod本身有提供FFT的功能，因此直接使用即可，无需再造车轮。FMod提供的FFT是DSP的一种，可以加载在Channel、ChannelGroup、ChannelControl上。这里，我们选择在系统创建并初始化之后直接加载到ChannelGroup上。",[31,226,229],{"className":227,"code":228,"language":36},[34],"FMOD::ChannelGroup *mastergroup;\n\nresult = system->createDSPByType(FMOD_DSP_TYPE_FFT, &fftdsp);\nif (result != FMOD_RESULT::FMOD_OK) break;\n\nresult = system->getMasterChannelGroup(&mastergroup);\nif (result != FMOD_RESULT::FMOD_OK) break;\n\nresult = mastergroup->addDSP(0, fftdsp);\nif (result != FMOD_RESULT::FMOD_OK) break;\n",[38,230,228],{"__ignoreMap":40},[11,232,233],{},"在每一次Ontick时进行FFT数据的获取：",[31,235,238],{"className":236,"code":237,"language":36},[34],"FMOD_DSP_PARAMETER_FFT *fft;\n\nchar* ts = new char[40];\nfftdsp->getParameterData(FMOD_DSP_FFT_SPECTRUMDATA, (void **)&fft, 0, 0, 0);\nOutputDebugStringA(\"\u003CFMODHolders> - Update Begin -\\n\");\nfor (int channel = 0; channel \u003C fft->numchannels; channel++){\nOutputDebugStringA(\"> -c-\");\nfor (int bin = 0; bin \u003C fft->length; bin++)\n{\nfloat val = fft->spectrum[channel][bin];\nsprintf(ts, \"> spec: %f \\n\", val);\nOutputDebugStringA(ts);\n}\n}\nOutputDebugStringA(\"\u003CFMODHolders> - Update End -\\n\");\ndelete ts;\n",[38,239,237],{"__ignoreMap":40},[11,241,242],{},"开始播放之后就可以看到fft数据已经正常输出了。",[11,244,245],{},"为了方便对fft的数据进行解析，需要获得采样率。",[31,247,250],{"className":248,"code":249,"language":36},[34],"system->getSoftwareFormat(&tnp, NULL, NULL);\n",[38,251,249],{"__ignoreMap":40},[11,253,254],{},"这样基本上音乐数据一块的验证就完成了。",{"title":40,"searchDepth":103,"depth":104,"links":256},[],"2015-05-21",{"layout":112,"status":113,"published":114,"author":259,"author_login":116,"author_email":117,"author_url":118,"wordpress_id":260,"wordpress_url":261,"date_gmt":262,"excerpt":263},{"display_name":116,"login":116,"email":117,"url":118},1272,"\u002F\u002F?p=1272","2015-05-21 04:09:36 +0000",{"type":8,"value":264},[265],[11,266,141],{},"\u002F2015-05-21-ue4-fmod-visulization",{"title":136,"description":141},"_legacy\u002F2015\u002F2015-05-21-ue4-fmod-visulization",[131,132],"53jn81bS0zOR_BQlQpG7tZSyEzEGNvhY4okZF-i8EkU",1788763180530]