2024년 9월 10일 화요일

DirectX - XAudio2 : 3D


※ C3646 이랑 C4430 오류 수정

지금까지 왜 멀쩡하게 컴파일 잘됐는지 모르겠는데

포맷하고 나니까 생긴 오류가 있었음




프로젝트 속성의 경로에서 WinSDK 를 먼저 포함시키고

다음 DXSDK 를 포함시키면 해결되는 문제였음



추가로 dx11 이랑 d11 를 둘다 사용하고 있길래

해당 코드들을 수정했음 



아무튼 3D 효과를 구현하려면

Xaudio2 초기화 하고 추가로 X3DAudio 를 초기화 하면 되는데

여기에 소리의 발생지와 수신자를 설정하고 이런저런 계산한 결과를

소스 보이스에 적용하면 완성



이번에도 대체로 마소 페이지에서 보고 공부함

How to: Integrate X3DAudio with XAudio2 - Win32 apps | Microsoft Learn



SoundClass.cpp

#include "SoundClass.h"

SoundClass::SoundClass()
{
	m_xAudio2 = 0;
	m_xAudio2MasteringVoice = 0;
	m_xAudio2SourceVoice = 0;
	m_matrixCoefficients = 0;
	m_dataBuffer = 0;
}

SoundClass::SoundClass(const SoundClass& other)
{
}

SoundClass::~SoundClass()
{
}

bool SoundClass::Initialize(const char* filePath)
{
	bool result;

	//XAudio 초기화
	result = InitializeXAudio();
	if (!result)
	{
		return false;
	}

	//재생할 wav 사운드 파일 로드
	result = LoadSoundFile(filePath);
	if (!result)
	{
		return false;
	}

	//X3DAudio 초기화
	result = InitializeXAudio3D();
	if (!result)
	{
		return false;
	}
	
	return true;
}

bool SoundClass::InitializeXAudio()
{
	HRESULT result;

	//COM 초기화
	result = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED);
	if (FAILED(result))
	{
		return false;
	}

	//XAudio2 생성
	result = ::XAudio2Create(&m_xAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR);
	if (FAILED(result))
	{
		return false;
	}

	//마스터링 보이스 생성
	result = m_xAudio2->CreateMasteringVoice(&m_xAudio2MasteringVoice);
	if (FAILED(result))
	{
		return false;
	}

	return true;
}


bool SoundClass::LoadSoundFile(const char* filePath)
{
	HRESULT result;

	WAVEFORMATEXTENSIBLE wfx;
	XAUDIO2_BUFFER buffer;

	ZeroMemory(&wfx, sizeof(WAVEFORMATEXTENSIBLE));
	ZeroMemory(&buffer, sizeof(XAUDIO2_BUFFER));

	//CreateFile을 사용하여 오디오 파일을 엽니다.
	HANDLE hFile = CreateFileA(
		filePath,
		GENERIC_READ,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		0,
		NULL);

	if (INVALID_HANDLE_VALUE == hFile)
	{
		return false;
	}

	if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
	{
		return false;
	}


	DWORD dwChunkSize;
	DWORD dwChunkPosition;

	//오디오 파일에서 'RIFF' 청크를 찾아 파일 형식을 확인합니다.
	//파일 타입이 fourccWAVE 또는 'XWMA'인지 확인합니다.
	result = FindChunk(hFile, fourccRIFF, dwChunkSize, dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}

	DWORD filetype;
	ReadChunkData(hFile, &filetype, sizeof(DWORD), dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}

	if (filetype != fourccWAVE)
	{
		return false;
	}


	//'fmt' 청크를 찾아 해당 내용을 WAVEFORMATEXTENSIBLE 구조체로 복사합니다.
	result = FindChunk(hFile, fourccFMT, dwChunkSize, dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}

	result = ReadChunkData(hFile, &wfx, dwChunkSize, dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}


	//'data' 청크를 찾아 해당 내용을 버퍼로 읽습니다.
	//오디오 버퍼를 fourccDATA 청크의 내용으로 채웁니다.
	result = FindChunk(hFile, fourccDATA, dwChunkSize, dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}

	m_dataBuffer = new BYTE[dwChunkSize];

	result = ReadChunkData(hFile, m_dataBuffer, dwChunkSize, dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}

	buffer.AudioBytes = dwChunkSize;  //오디오 버퍼의 크기. Byte로
	buffer.pAudioData = m_dataBuffer;  //버퍼에 들어있는 오디오 데이터
	buffer.Flags = XAUDIO2_END_OF_STREAM; 	//이 버퍼 이후에 더 이상 데이터가 없다고 설정합니다.

	result = m_xAudio2->CreateSourceVoice(&m_xAudio2SourceVoice, (WAVEFORMATEX*)&wfx);
	if (FAILED(result))
	{
		return false;
	}


	result = m_xAudio2SourceVoice->SubmitSourceBuffer(&buffer);
	if (FAILED(result))
	{
		return false;
	}

	return true;
}

bool SoundClass::InitializeXAudio3D()
{
	HRESULT result;

	//소스, 마스터링 보이스의 채널수를 가져옴
	m_xAudio2SourceVoice->GetVoiceDetails(&m_sourceDetails);
	m_xAudio2MasteringVoice->GetVoiceDetails(&m_masteringDetails);

	//마스터링 보이스 채널 마스크를 가져옵니다.
	DWORD dwChannelMask;
	m_xAudio2MasteringVoice->GetChannelMask(&dwChannelMask);

	result = X3DAudioInitialize(dwChannelMask, X3DAUDIO_SPEED_OF_SOUND, m_x3DInstance);
	if (FAILED(result))
	{
		return false;
	}

	//소리 수신기 설정
	ZeroMemory(&m_listener, sizeof(X3DAUDIO_LISTENER));
	m_listener.Position = { 0.0f, 0.0f, 0.0f };
	m_listener.OrientFront = { 0.0f, 0.0f, 1.0f };
	m_listener.OrientTop = { 0.0f, 1.0f, 0.0f };

	//소리 방출기 설정 
	ZeroMemory(&m_emitter, sizeof(X3DAUDIO_EMITTER));
	m_emitter.Position = { 0.0f, 0.0f, 0.0f };
	m_emitter.OrientFront = { 0.0f, 0.0f, 1.0f };
	m_emitter.OrientTop = { 0.0f, 1.0f, 0.0f };
	m_emitter.DopplerScaler = 1.0f;
	m_emitter.CurveDistanceScaler = 1.0f;
	m_emitter.ChannelCount = 1;


	//X3DAudioCalculate에서 계산된 결과를 반환하는 데 사용됨
	m_matrixCoefficients = new FLOAT32[m_masteringDetails.InputChannels * m_sourceDetails.InputChannels];
	ZeroMemory(&m_DSPSettings, sizeof(X3DAUDIO_DSP_SETTINGS));
	m_DSPSettings.SrcChannelCount = m_sourceDetails.InputChannels;
	m_DSPSettings.DstChannelCount = m_masteringDetails.InputChannels;
	m_DSPSettings.pMatrixCoefficients = m_matrixCoefficients;

	//음성에 대한 새 설정을 계산
	X3DAudioCalculate(m_x3DInstance, &m_listener, &m_emitter,
		X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER | X3DAUDIO_CALCULATE_LPF_DIRECT | X3DAUDIO_CALCULATE_REVERB,
		&m_DSPSettings);

	//볼륨 및 피치 값을 원본 음성에 적용
	m_xAudio2SourceVoice->SetOutputMatrix(m_xAudio2MasteringVoice, m_DSPSettings.SrcChannelCount, m_DSPSettings.DstChannelCount, m_DSPSettings.pMatrixCoefficients);
	m_xAudio2SourceVoice->SetFrequencyRatio(m_DSPSettings.DopplerFactor);

	////계산된 반향 수준을 서브믹스 음성에 적용
	//m_xAudio2SourceVoice->SetOutputMatrix(m_xAudio2MasteringVoice, sourceDetails.InputChannels, m_DSPSettings.DstChannelCount, &m_DSPSettings.ReverbLevel);

	////계산된 로우 패스 필터 직접 계수를 원본 음성에 적용
	//XAUDIO2_FILTER_PARAMETERS FilterParameters = { LowPassFilter, 2.0f * sinf(X3DAUDIO_PI / 6.0f * m_DSPSettings.LPFDirectCoefficient), 1.0f };
	//m_xAudio2SourceVoice->SetFilterParameters(&FilterParameters);

	return true;
}


bool SoundClass::PlayAudio()
{
	HRESULT result;

	//소스 보이스 재생
	result = m_xAudio2SourceVoice->Start(0, XAUDIO2_COMMIT_NOW);
	if (FAILED(result))
	{
		return false;
	}

	return true;
}

void SoundClass::UpdateListener(const DirectX::XMVECTOR position, const DirectX::XMVECTOR lookAt, const DirectX::XMVECTOR up)
{
	m_listener.Position.x = position.m128_f32[0];
	m_listener.Position.y = position.m128_f32[1];
	m_listener.Position.z = position.m128_f32[2];

	m_listener.OrientFront.x = lookAt.m128_f32[0];
	m_listener.OrientFront.y = lookAt.m128_f32[1];
	m_listener.OrientFront.z = lookAt.m128_f32[2];

	m_listener.OrientTop.x = up.m128_f32[0];
	m_listener.OrientTop.y = up.m128_f32[1];
	m_listener.OrientTop.z = up.m128_f32[2];

	return;
}

void SoundClass::UpdateEmitter(const DirectX::XMVECTOR position, const DirectX::XMVECTOR lookAt, const DirectX::XMVECTOR up)
{
	m_emitter.Position.x = position.m128_f32[0];
	m_emitter.Position.y = position.m128_f32[1];
	m_emitter.Position.z = position.m128_f32[2];

	m_emitter.OrientFront.x = lookAt.m128_f32[0];
	m_emitter.OrientFront.y = lookAt.m128_f32[1];
	m_emitter.OrientFront.z = lookAt.m128_f32[2];

	m_emitter.OrientTop.x = up.m128_f32[0];
	m_emitter.OrientTop.y = up.m128_f32[1];
	m_emitter.OrientTop.z = up.m128_f32[2];

	return;
}

bool SoundClass::Frame()
{
	HRESULT result;

	//음성에 대한 새 설정을 계산
	X3DAudioCalculate(m_x3DInstance, &m_listener, &m_emitter,
		X3DAUDIO_CALCULATE_MATRIX | X3DAUDIO_CALCULATE_DOPPLER | X3DAUDIO_CALCULATE_LPF_DIRECT | X3DAUDIO_CALCULATE_REVERB,
		&m_DSPSettings);

	//볼륨 및 피치 값을 원본 음성에 적용
	result = m_xAudio2SourceVoice->SetOutputMatrix(m_xAudio2MasteringVoice, m_sourceDetails.InputChannels, m_DSPSettings.DstChannelCount, m_DSPSettings.pMatrixCoefficients);
	if (FAILED(result))
	{
		return false;
	}

	result = m_xAudio2SourceVoice->SetFrequencyRatio(m_DSPSettings.DopplerFactor);
	if (FAILED(result))
	{
		return false;
	}

	return true;
}

HRESULT SoundClass::FindChunk(HANDLE hFile, DWORD fourcc, DWORD& dwChunkSize, DWORD& dwChunkDataPosition)
{
	HRESULT hr = S_OK;
	if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
		return HRESULT_FROM_WIN32(GetLastError());

	DWORD dwChunkType;
	DWORD dwChunkDataSize;
	DWORD dwRIFFDataSize = 0;
	DWORD dwFileType;
	DWORD bytesRead = 0;
	DWORD dwOffset = 0;

	while (hr == S_OK)
	{
		DWORD dwRead;
		if (0 == ReadFile(hFile, &dwChunkType, sizeof(DWORD), &dwRead, NULL))
			hr = HRESULT_FROM_WIN32(GetLastError());

		if (0 == ReadFile(hFile, &dwChunkDataSize, sizeof(DWORD), &dwRead, NULL))
			hr = HRESULT_FROM_WIN32(GetLastError());

		switch (dwChunkType)
		{
		case fourccRIFF:
			dwRIFFDataSize = dwChunkDataSize;
			dwChunkDataSize = 4;
			if (0 == ReadFile(hFile, &dwFileType, sizeof(DWORD), &dwRead, NULL))
				hr = HRESULT_FROM_WIN32(GetLastError());
			break;

		default:
			if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, dwChunkDataSize, NULL, FILE_CURRENT))
				return HRESULT_FROM_WIN32(GetLastError());
		}

		dwOffset += sizeof(DWORD) * 2;

		if (dwChunkType == fourcc)
		{
			dwChunkSize = dwChunkDataSize;
			dwChunkDataPosition = dwOffset;
			return S_OK;
		}

		dwOffset += dwChunkDataSize;

		if (bytesRead >= dwRIFFDataSize) return S_FALSE;

	}

	return S_OK;
}

HRESULT SoundClass::ReadChunkData(HANDLE hFile, void* buffer, DWORD buffersize, DWORD bufferoffset)
{
	HRESULT result;

	if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, bufferoffset, NULL, FILE_BEGIN))
	{
		return HRESULT_FROM_WIN32(GetLastError());
	}
		
	DWORD dwRead;
	if (0 == ReadFile(hFile, buffer, buffersize, &dwRead, NULL))
	{
		result = HRESULT_FROM_WIN32(GetLastError());
	}
		
	return S_OK;
}

void SoundClass::Shutdown()
{

	if (m_dataBuffer)
	{
		delete[] m_dataBuffer;
		m_dataBuffer = 0;
	}

	if (m_matrixCoefficients)
	{
		delete[] m_matrixCoefficients;
		m_matrixCoefficients = 0;
	}

	if (m_xAudio2SourceVoice)
	{
		m_xAudio2SourceVoice->DestroyVoice();
		m_xAudio2SourceVoice = 0;
	}


	if (m_xAudio2MasteringVoice)
	{
		m_xAudio2MasteringVoice->DestroyVoice();
		m_xAudio2MasteringVoice = 0;
	}

	if (m_xAudio2)
	{
		m_xAudio2->Release();
		m_xAudio2 = 0;
	}

	// Uninitialize COM.
	::CoUninitialize();

	return;
}

도플러 효과 및 이런저런 기능들이 사용 가능 했었음



사운드 출력 위치를 보면서

화면을 돌리거나 이동하면서 테스트 하기 쉽게

삼각형을 하나 그리고, 카메라를 향해 계속 회전시킴


void ApplicationClass::Render(HWND hwnd, InputClass* pInputClass)
{
	XMMATRIX world, view, proj;

	m_CameraClass->Render();

	//3D RenderTarget 초기화(특정 컬러로)
	m_Direct3D->BeginScene(0.0f, 0.0f, 0.2f, 1.0f);

	//2D RenderTarget 초기화
	m_TextClass->BeginDraw();

	m_Direct3D->GetWorldMatrix(world);
	m_Direct3D->GetProjectionMatrix(proj);
	m_CameraClass->GetViewMatrix(view);

	//소리 발생지 0,0,0의 위치를 시각적으로 나타내기 위해 삼각형을 렌더링
	{
		XMFLOAT3 direction, origin;
		m_CameraClass->GetPosition(direction);
		origin = XMFLOAT3(0.0f, 0.0f, 0.0f);

		//카메라를 향해 항상 바라보게
		float rotY;
		rotY = atan2f(origin.x - direction.x, origin.z - direction.z);

		//모델을 회전시킴
		world = world * XMMatrixRotationY(rotY);

		//삼각형 렌더링
		m_ShaderManager->GetColorShader()->Render(m_Direct3D->GetDeviceContext(), world, view, proj);
		m_triangle->Render(m_Direct3D->GetDeviceContext());
	}

	//UI 렌더링
	m_uiManager->Frame(m_Direct3D, hwnd, m_ShaderManager, m_TextClass, m_CameraClass, pInputClass);

	m_TextClass->EndDraw();
	m_Direct3D->EndScene();

	return;
}


이상없이 잘 들렸음

당연하겠지만 오디오 소스가 모노가 아니라 스테레오일 경우

소리가 아예 안나는 문제가 있었음



재생할 원본 사운드가 스테레오라도 모노로 변환하여 사용하고 싶었지만

기술 부족


전체 소스코드 :  h117562/Tutorial_XAudio2_3D (github.com)

2024년 9월 8일 일요일

DirectX - XAudio2

이전 내용을 토대로 라이브러리만 변경하여

사운드 출력 클래스를 구현해 보았음


Dsound와 동일하게 사운드 파일의 확장자 및 정보들을

바이너리 형태로 읽어 저장하고 재생함


다만 이번에는 MS 가이드 그대로 코드 짜집기 했음


SoundClass.h

#ifndef _SOUNDCLASS_H_
#define _SOUNDCLASS_H_


#ifdef _XBOX //Big-Endian
#define fourccRIFF 'RIFF'
#define fourccDATA 'data'
#define fourccFMT 'fmt '
#define fourccWAVE 'WAVE'
#define fourccXWMA 'XWMA'
#define fourccDPDS 'dpds'
#endif

#ifndef _XBOX //Little-Endian
#define fourccRIFF 'FFIR'
#define fourccDATA 'atad'
#define fourccFMT ' tmf'
#define fourccWAVE 'EVAW'
#define fourccXWMA 'AMWX'
#define fourccDPDS 'sdpd'
#endif

#pragma comment(lib, "xaudio2.lib")

#include <xaudio2.h>
#include <windows.h>

class SoundClass
{
public:
	SoundClass();
	SoundClass(const SoundClass&);
	~SoundClass();

	bool Initialize(HWND, const char*);
	bool PlayAudio();
	void Shutdown();

private:
	bool InitializeXAudio(HWND);
	bool LoadSoundFile(const char*);
	HRESULT FindChunk(HANDLE, DWORD, DWORD&, DWORD&);
	HRESULT ReadChunkData(HANDLE, void*, DWORD, DWORD);

private:
	IXAudio2* m_xAudio2;
	IXAudio2MasteringVoice* m_xAudio2MasteringVoice;
	IXAudio2SourceVoice* m_xAudio2SourceVoice;
};

#endif



SoundClass.cpp

#include "SoundClass.h"

SoundClass::SoundClass()
{
	m_xAudio2 = 0;
	m_xAudio2MasteringVoice = 0;
	m_xAudio2SourceVoice = 0;
}

SoundClass::SoundClass(const SoundClass& other)
{
}

SoundClass::~SoundClass()
{
}

bool SoundClass::Initialize(HWND hwnd, const char* filePath)
{
	bool result;

	//XAudio 초기화
	result = InitializeXAudio(hwnd);
	if (!result)
	{
		return false;
	}
	
	//wav 파일 로드
	result = LoadSoundFile(filePath);
	if (!result)
	{
		return false;
	}

	return true;
}

bool SoundClass::InitializeXAudio(HWND hwnd)
{
	HRESULT result;

	//COM 초기화
	result = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED);
	if (FAILED(result))
	{
		return false;
	}

	//XAudio2 초기화
	result = ::XAudio2Create(&m_xAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR);
	if (FAILED(result))
	{
		return false;
	}

	//마스터링 보이스 생성
	result = m_xAudio2->CreateMasteringVoice(&m_xAudio2MasteringVoice);
	if (FAILED(result))
	{
		return false;
	}

	return true;
}

bool SoundClass::LoadSoundFile(const char* filePath)
{
	HRESULT result;

	WAVEFORMATEXTENSIBLE wfx = { 0 };
	XAUDIO2_BUFFER buffer = { 0 };

	//CreateFile을 사용하여 오디오 파일을 엽니다.
	HANDLE hFile = CreateFileA(
		filePath,
		GENERIC_READ,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		0,
		NULL);

	if (INVALID_HANDLE_VALUE == hFile)
	{
		return false;
	}
		
	if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
	{
		return false;
	}


	DWORD dwChunkSize;
	DWORD dwChunkPosition;

	//오디오 파일에서 'RIFF' 청크를 찾아 파일 형식을 확인합니다.
	//check the file type, should be fourccWAVE or 'XWMA'
	result = FindChunk(hFile, fourccRIFF, dwChunkSize, dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}

	//RIFF 청크를 읽어 파일 타입을 ㅓㅈ장
	DWORD filetype;
	ReadChunkData(hFile, &filetype, sizeof(DWORD), dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}

	//Wav 확장자가 아닐 경우 리턴
	if (filetype != fourccWAVE)
	{
		return false;
	}


	//'fmt' 청크를 찾아 해당 내용을 WAVEFORMATEXTENSIBLE 구조체로 저장합니다.
	result = FindChunk(hFile, fourccFMT, dwChunkSize, dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}

	result = ReadChunkData(hFile, &wfx, dwChunkSize, dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}
		

	//'data' 청크를 찾아 해당 내용을 버퍼로 읽습니다.
	//fill out the audio data buffer with the contents of the fourccDATA chunk
	result = FindChunk(hFile, fourccDATA, dwChunkSize, dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}

	//읽은 데이터를 XAudio 버퍼에 저장
	BYTE* pDataBuffer = new BYTE[dwChunkSize];

	result = ReadChunkData(hFile, pDataBuffer, dwChunkSize, dwChunkPosition);
	if (FAILED(result))
	{
		return false;
	}

	buffer.AudioBytes = dwChunkSize;  //size of the audio buffer in bytes
	buffer.pAudioData = pDataBuffer;  //buffer containing audio data
	buffer.Flags = XAUDIO2_END_OF_STREAM; // tell the source voice not to expect any data after this buffer


	//소스 보이스 생성
	result = m_xAudio2->CreateSourceVoice(&m_xAudio2SourceVoice, (WAVEFORMATEX*)&wfx);
	if (FAILED(result))
	{
		return false;
	}

	result = m_xAudio2SourceVoice->SubmitSourceBuffer(&buffer);
	if (FAILED(result))
	{
		return false;
	}

	return true;
}

bool SoundClass::PlayAudio()
{
	HRESULT result;
	
	//오디오 재생
	result = m_xAudio2SourceVoice->Start(0);
	if (FAILED(result))
	{
		return false;
	}

	return true;
}

HRESULT SoundClass::FindChunk(HANDLE hFile, DWORD fourcc, DWORD& dwChunkSize, DWORD& dwChunkDataPosition)
{
	HRESULT hr = S_OK;
	if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, 0, NULL, FILE_BEGIN))
		return HRESULT_FROM_WIN32(GetLastError());

	DWORD dwChunkType;
	DWORD dwChunkDataSize;
	DWORD dwRIFFDataSize = 0;
	DWORD dwFileType;
	DWORD bytesRead = 0;
	DWORD dwOffset = 0;

	while (hr == S_OK)
	{
		DWORD dwRead;
		if (0 == ReadFile(hFile, &dwChunkType, sizeof(DWORD), &dwRead, NULL))
			hr = HRESULT_FROM_WIN32(GetLastError());

		if (0 == ReadFile(hFile, &dwChunkDataSize, sizeof(DWORD), &dwRead, NULL))
			hr = HRESULT_FROM_WIN32(GetLastError());

		switch (dwChunkType)
		{
		case fourccRIFF:
			dwRIFFDataSize = dwChunkDataSize;
			dwChunkDataSize = 4;
			if (0 == ReadFile(hFile, &dwFileType, sizeof(DWORD), &dwRead, NULL))
				hr = HRESULT_FROM_WIN32(GetLastError());
			break;

		default:
			if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, dwChunkDataSize, NULL, FILE_CURRENT))
				return HRESULT_FROM_WIN32(GetLastError());
		}

		dwOffset += sizeof(DWORD) * 2;

		if (dwChunkType == fourcc)
		{
			dwChunkSize = dwChunkDataSize;
			dwChunkDataPosition = dwOffset;
			return S_OK;
		}

		dwOffset += dwChunkDataSize;

		if (bytesRead >= dwRIFFDataSize) return S_FALSE;

	}

	return S_OK;
}

HRESULT SoundClass::ReadChunkData(HANDLE hFile, void* buffer, DWORD buffersize, DWORD bufferoffset)
{
	HRESULT result;

	if (INVALID_SET_FILE_POINTER == SetFilePointer(hFile, bufferoffset, NULL, FILE_BEGIN))
	{
		return HRESULT_FROM_WIN32(GetLastError());
	}
		
	DWORD dwRead;
	if (0 == ReadFile(hFile, buffer, buffersize, &dwRead, NULL))
	{
		result = HRESULT_FROM_WIN32(GetLastError());
	}
		
	return S_OK;
}

void SoundClass::Shutdown()
{

	if (m_xAudio2SourceVoice)
	{
		m_xAudio2SourceVoice->DestroyVoice();
		m_xAudio2SourceVoice = 0;
	}


	if (m_xAudio2MasteringVoice)
	{
		m_xAudio2MasteringVoice->DestroyVoice();
		m_xAudio2MasteringVoice = 0;
	}

	if (m_xAudio2)
	{
		m_xAudio2->Release();
		m_xAudio2 = 0;
	}

	return;
}


소리만 나오니 쓸모 없는 사진




참고 자료 : XAudio2 programming guide - Win32 apps | Microsoft Learn

전체 코드 : h117562/Tutorial_XAudio2 (github.com)

2024년 9월 3일 화요일

wav 파일 확장자

 

사운드 확장자 파일들 mp3 wav flac등 여러가지가 있는데

그 중에 wav 파일을 조금 정리해보려고 한다



오디오에 관하여 전문가는 아니지만 쓰기 위해서

최소한의 공부는 해두고 가야할 것 같기 때문



WAVE 파일 형식은 컴퓨터에 오디오 비트스트림을 저장하기 위한

오디오 파일 형식이다.



PCM(Pulse-code Modulation) 방식

아날로그 사운드를 디지털 방식으로 변환하기 위한 방법 



wav 는 mp3 와 달리 압축 없는 원본 사운드 파일이며

고로 High Fidelity 오디오 데이터이다 - 손실이 거의 없다는 뜻



오디오의 확장자, 데이터, 채널 수...등 여러 요소들을 저장하고 읽기 위해

청크 단위로 나눠서 저장함




그 청크 데이터의 첫번째 요소

RIFF는 Resource Interchange File Format 

오디오 확장자 및 파일 크기를 나타내는 부분임


4 4 4 바이트로 ID는 "RIFF" 아스키 값이 저장

FileSize(ChunkSize)는 전체 파일 크기에서 8바이트를 뺀 값이며


그리고 FileFormatID(Format)은 wav 확장자를 의미하는 "WAVE"가 저장되어 있음 

똑같이 ASCII 형




다음은 fmt 청크로 얘는 오디오 데이터 포맷을 설명함

첫번째 값은 ID로 "fmt" 아스키를 저장


두번째는 fmt 청크의 크기를 나타내는데 ID와 

fmt ChunkSize를 제외한 나머지 크기를 의미함 - 16바이트


AudioFormat은 PCM 의 경우 1이 저장


NumChannels 는 채널 개수,

모노는 채널수가 하나인 좌우 개념 없는 사운드고

스테레오는 좌우 음향, 사플 대충 그런 느낌 - 채널 두개


Frequence(SampleRate) - 초당 행하는 샘플링 횟수

보통 44100 Hz (44.1 kHz) 임


ByteRate(BytePerSec) - 초당 비트전송률

(샘플 레이트 x 채널 갯수 x 샘플 당 비트수) 로 구할 수 있음

여기에 8로 나누어 바이트로 변환하면 실제 저장된 값이 나옴

ex) 44.1kHz * 2(stereo) * 16bit = 1411.2 kbps



오디오 속성 들가면 나와있음


BlockAlign(BytePerBlock) - 블럭 당 바이트 수

(채널 갯수 x 샘플 당 비트 수) / 8


BitsPerSample - 각 샘플 당 비트 수

8비트 16비트 등이 있음



마지막 청크로 Data 청크는

ID "data" ASCII

DataSize - (샘플 갯수 x 채널 갯수 x 샘플 당 비트 수) / 8

SampledData - N 바이트


이렇게 실제 오디오 데이터 관련해서 저장됨



이게 바이트가 저장되는 순서는 또 다르다


Identifier 는 ASCII라 1 2 3 4 이렇게 순서대로 저장

나머지는 0인 비트가 자주 있어서 인지 모르겠는데

4 3 2 1 거꾸로 저장됨

이는 Big Endian 과 Little Endian 이라 한다


예를 들어 RIFF 청크에 이런 데이터가 있다고 치면



ID "RIFF"와 뒷부분 포맷 "WAVE" 는 Big Endian이라 순서대로 저장되는데 

가운데는 Little로 거꾸로니까 실제로 읽을때는

00 00 08 24 가 된다.

이는 10진수로 2084이므로 실제 파일 사이즈는 2084 + 8 인

2 KB (2092 byte) 가 된다. /1024


참고 사이트 : WAV - 위키백과, 우리 모두의 백과사전 (wikipedia.org)



 

2024년 9월 2일 월요일

DirectX - DirectSound

+ 메인보드가 하도 오래돼서 맛이 감

추가 드라이브가 있으면 부팅이 안돼는데 ㅋㅋㅋ

덕분에 컴퓨터도 초기화;; 어이없어서 그냥 외장하드 하나사서

새로운 마음으로 출발함 


DirectX11에서 오디오를 출력하려고 했는데

dSound로 3D효과 구현이 생각처럼 잘 안돼서

그냥 XAudio 로 갈아타려고 한다.


그래도 단순히 출력만 하는건 해두었기 때문에 일단은 올림


#include "SoundClass.h"

SoundClass::SoundClass()
{
	m_directSound = 0;
	m_primaryBuffer = 0;
	m_secondaryBuffer = 0;
	m_waveData = 0;
}

SoundClass::SoundClass(const SoundClass& other)
{
}

SoundClass::~SoundClass()
{
}

bool SoundClass::Initialize(HWND hwnd, const char* filePath)
{
	bool result;


	//DirectSound 초기화
	result = InitializeDirectSound(hwnd);
	if (!result)
	{
		return false;
	}

	//오디오 파일 읽기
	result = LoadSoundFile(filePath);
	if (!result)
	{
		return false;
	}

	return true;
}

bool SoundClass::InitializeDirectSound(HWND hwnd)
{
	HRESULT result;

	//DSound 생성
	result = DirectSoundCreate8(NULL, &m_directSound, NULL);
	if (FAILED(result))
	{
		return false;
	}

	//협력 수준 세팅
	result = m_directSound->SetCooperativeLevel(hwnd, DSSCL_PRIORITY);
	if (FAILED(result))
	{
		return false;
	}

	//primary buffer 초기화
	DSBUFFERDESC bufferDesc;
	bufferDesc.dwSize = sizeof(DSBUFFERDESC);
	bufferDesc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME;
	bufferDesc.dwBufferBytes = 0;
	bufferDesc.dwReserved = 0;
	bufferDesc.lpwfxFormat = NULL;
	bufferDesc.guid3DAlgorithm = GUID_NULL;

	result = m_directSound->CreateSoundBuffer(&bufferDesc, &m_primaryBuffer, NULL);
	if (FAILED(result))
	{
		return false;
	}

	return true;
}

bool SoundClass::LoadSoundFile(const char* filePath)
{
	HRESULT result;

	unsigned long dataSize, bufferSize;
	unsigned char* bufferPtr = 0;

	//바이너리로 읽기
	std::ifstream file(filePath, std::ios::binary);
	if (!file.is_open())
	{
		return false;
	}

	//파일 전체의 크기를 가져옴
	file.seekg(0, std::ios::end);
	dataSize = file.tellg();
	m_waveData = new unsigned char[dataSize];

	//wav 파일 데이터 저장
	file.seekg(0, std::ios::beg);
	file.read((char*)m_waveData, dataSize);

	//Wav 포맷 설정
	WAVEFORMATEX waveFormat;
	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nSamplesPerSec = 44100;//원래는
	waveFormat.wBitsPerSample = 16;//파일 데이터에서
	waveFormat.nChannels = 2;//직접 읽어야 함
	waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
	waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
	waveFormat.cbSize = 0;

	//secondary buffer 초기화
	DSBUFFERDESC bufferDesc;
	bufferDesc.dwSize = sizeof(DSBUFFERDESC);
	bufferDesc.dwFlags = DSBCAPS_CTRLVOLUME;
	bufferDesc.dwBufferBytes = dataSize;
	bufferDesc.dwReserved = 0;
	bufferDesc.lpwfxFormat = &waveFormat;
	bufferDesc.guid3DAlgorithm = GUID_NULL;

	result = m_directSound->CreateSoundBuffer(&bufferDesc, &m_secondaryBuffer, NULL);
	if (FAILED(result))
	{
		return false;
	}

	//메모리 직접 접근
	result = m_secondaryBuffer->Lock(0, dataSize, (void**)&bufferPtr, (DWORD*)&bufferSize, NULL, 0, 0);
	if (FAILED(result))
	{
		return false;
	}

	//복사
	memcpy(bufferPtr, m_waveData, dataSize);

	//락 해제
	result = m_secondaryBuffer->Unlock(bufferPtr, bufferSize, NULL, 0);
	if (FAILED(result))
	{
		return false;
	}

	return true;
}

bool SoundClass::PlayAudio()
{
	HRESULT result;

	result = m_secondaryBuffer->SetCurrentPosition(0);
	if (FAILED(result))
	{
		return false;
	}

	result = m_secondaryBuffer->SetVolume(DSBVOLUME_MAX);
	if (FAILED(result))
	{
		return false;
	}

	result = m_secondaryBuffer->Play(0, 0, 0);
	if (FAILED(result))
	{
		return false;
	}
	
	return true;
}

void SoundClass::Shutdown()
{
	if (m_waveData)
	{
		delete[] m_waveData;
		m_waveData = 0;
	}

	if (m_secondaryBuffer)
	{
		m_secondaryBuffer->Release();
		m_secondaryBuffer = 0;
	}

	if (m_primaryBuffer)
	{
		m_primaryBuffer->Release();
		m_primaryBuffer = 0;
	}

	if (m_directSound)
	{
		m_directSound->Release();
		m_directSound = 0;
	}

	return;
}


깃 링크

h117562/Tutorial_DirectSound: DirectSound Example (github.com)

c++ thread.h

 c++에서 쓰레드 돌릴려면 thread.h 헤더를 쓰면 되는데 이 친구는 쓰레드가 아직 실행 중인지, 아니면 강제 종료하거나 하는 함수가 없어서 조금 아쉬운 애다. std::thread 는 로컬 변수로 선언하든 new 동적 할당을 하든 start 함...