2019-12-26 14:45:42 -05:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
2019-11-26 17:28:51 -05:00
|
|
|
|
2021-04-29 19:32:06 -04:00
|
|
|
#include "WmfAudioEncoder.h"
|
2019-11-28 08:44:18 -05:00
|
|
|
|
2021-04-29 19:32:06 -04:00
|
|
|
#if PLATFORM_WINDOWS
|
2019-11-26 17:28:51 -05:00
|
|
|
|
2021-04-29 19:32:06 -04:00
|
|
|
#include "AVEncoder.h"
|
|
|
|
|
#include "Logging/LogMacros.h"
|
|
|
|
|
#include "MicrosoftCommon.h"
|
|
|
|
|
#include "AudioEncoder.h"
|
|
|
|
|
#include "IMFSampleWrapper.h"
|
2019-11-28 08:44:18 -05:00
|
|
|
|
2019-11-26 17:28:51 -05:00
|
|
|
namespace AVEncoder
|
|
|
|
|
{
|
|
|
|
|
class FWmfAudioEncoder : public FAudioEncoder
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
|
|
enum class ECodecType
|
|
|
|
|
{
|
|
|
|
|
AAC
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
FWmfAudioEncoder(ECodecType CodecType);
|
|
|
|
|
~FWmfAudioEncoder() override;
|
|
|
|
|
|
|
|
|
|
const TCHAR* GetName() const override;
|
|
|
|
|
const TCHAR* GetType() const override;
|
2021-04-29 19:32:06 -04:00
|
|
|
bool Initialize(const FAudioConfig& InConfig) override;
|
2019-11-26 17:28:51 -05:00
|
|
|
void Shutdown() override;
|
|
|
|
|
void Encode(const FAudioFrame& Frame) override;
|
2021-04-29 19:32:06 -04:00
|
|
|
FAudioConfig GetConfig() const override;
|
2019-11-26 17:28:51 -05:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
bool SetInputType();
|
|
|
|
|
bool SetOutputType();
|
|
|
|
|
bool RetrieveStreamInfo();
|
|
|
|
|
bool StartStreaming();
|
|
|
|
|
|
|
|
|
|
FIMFSampleWrapper CreateInputSample(const uint8* SampleData, uint32 Size, FTimespan Timestamp, FTimespan Duration);
|
|
|
|
|
FIMFSampleWrapper GetOutputSample();
|
|
|
|
|
|
|
|
|
|
ECodecType CodecType;
|
|
|
|
|
FString Name;
|
|
|
|
|
FString Type;
|
|
|
|
|
|
2021-04-29 19:32:06 -04:00
|
|
|
FAudioConfig Config;
|
2019-11-26 17:28:51 -05:00
|
|
|
TRefCountPtr<IMFTransform> Encoder;
|
|
|
|
|
TRefCountPtr<IMFMediaType> OutputType;
|
|
|
|
|
MFT_INPUT_STREAM_INFO InputStreamInfo = {};
|
|
|
|
|
MFT_OUTPUT_STREAM_INFO OutputStreamInfo = {};
|
|
|
|
|
|
|
|
|
|
// Keep this as a member variable, to reuse the memory allocation
|
|
|
|
|
// It's used to convert float audio data to PCM
|
|
|
|
|
Audio::TSampleBuffer<int16> PCM16;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// FWmfAudioEncoder implementation
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
FWmfAudioEncoder::FWmfAudioEncoder(ECodecType CodecType)
|
|
|
|
|
: CodecType(CodecType)
|
|
|
|
|
{
|
|
|
|
|
switch (CodecType)
|
|
|
|
|
{
|
|
|
|
|
case ECodecType::AAC:
|
|
|
|
|
Name = "aac.wmf";
|
|
|
|
|
Type = "aac";
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
checkNoEntry();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FWmfAudioEncoder::~FWmfAudioEncoder()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TCHAR* FWmfAudioEncoder::GetName() const
|
|
|
|
|
{
|
|
|
|
|
return *Name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const TCHAR* FWmfAudioEncoder::GetType() const
|
|
|
|
|
{
|
|
|
|
|
return *Type;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
//
|
|
|
|
|
// Utility function to validate if a given encoder parameter has an acceptable value
|
|
|
|
|
//
|
|
|
|
|
bool ValidateValue(const TCHAR* Name, uint32 Value, const TArray<uint32>& SupportedValues, FString& OutErr)
|
|
|
|
|
{
|
|
|
|
|
for(uint32 V : SupportedValues)
|
|
|
|
|
{
|
|
|
|
|
if (Value == V)
|
|
|
|
|
{
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OutErr = FString(Name) + TEXT(" must be (");
|
|
|
|
|
for(uint32 V : SupportedValues)
|
|
|
|
|
{
|
|
|
|
|
OutErr += FString::Printf(
|
|
|
|
|
TEXT("%s%u"),
|
|
|
|
|
OutErr.IsEmpty() ? TEXT("") : TEXT(", "),
|
|
|
|
|
V);
|
|
|
|
|
}
|
|
|
|
|
OutErr += FString::Printf(TEXT("). %u was specified."), Value);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-29 19:32:06 -04:00
|
|
|
bool FWmfAudioEncoder::Initialize(const FAudioConfig& InConfig)
|
2019-11-26 17:28:51 -05:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Validate config depending on the codec
|
|
|
|
|
//
|
|
|
|
|
if (CodecType == ECodecType::AAC)
|
|
|
|
|
{
|
|
|
|
|
// See https://docs.microsoft.com/en-us/windows/desktop/medfound/aac-encoder for details
|
|
|
|
|
FString ErrorStr;
|
|
|
|
|
if (
|
|
|
|
|
!ValidateValue(TEXT("AAC Bitrate"), InConfig.Bitrate, {12000*8, 16000*8, 20000*8, 24000*8}, ErrorStr) ||
|
|
|
|
|
!ValidateValue(TEXT("AAC Samplerate"), InConfig.Samplerate, { 44100, 48000, 0 }, ErrorStr) ||
|
|
|
|
|
!ValidateValue(TEXT("AAC NumChannels"), InConfig.NumChannels, {1,2,6}, ErrorStr))
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogAVEncoder, Error, TEXT("%s"), *ErrorStr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
checkNoEntry();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogAVEncoder, Log, TEXT("AudioEncoder config: %d channels, %d Hz, %.2f Kbps"), InConfig.NumChannels, InConfig.Samplerate, InConfig.Bitrate / 1000.0f);
|
|
|
|
|
|
|
|
|
|
Config = InConfig;
|
|
|
|
|
|
|
|
|
|
const GUID* CodecGuid=nullptr;
|
|
|
|
|
if (CodecType == ECodecType::AAC)
|
|
|
|
|
{
|
|
|
|
|
CodecGuid = &CLSID_AACMFTEncoder;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
checkNoEntry();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CHECK_HR(CoCreateInstance(*CodecGuid, nullptr, CLSCTX_INPROC_SERVER, IID_IMFTransform, reinterpret_cast<void**>(&Encoder)));
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// NOTES
|
|
|
|
|
// MP3: The output type must be set before the input. See https://docs.microsoft.com/en-us/windows/win32/medfound/mp3-audio-encoder
|
|
|
|
|
// AAC: Output or input type can be set in either order. Input type first, or output type first.
|
|
|
|
|
|
|
|
|
|
if (!SetInputType() || !SetOutputType() || !RetrieveStreamInfo() || !StartStreaming())
|
|
|
|
|
{
|
|
|
|
|
Encoder->Release();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FWmfAudioEncoder::SetInputType()
|
|
|
|
|
{
|
|
|
|
|
TRefCountPtr<IMFMediaType> MediaType;
|
|
|
|
|
CHECK_HR(MFCreateMediaType(MediaType.GetInitReference()));
|
|
|
|
|
CHECK_HR(MediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
|
|
|
|
|
CHECK_HR(MediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM));
|
|
|
|
|
CHECK_HR(MediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 16)); // the only value supported
|
|
|
|
|
CHECK_HR(MediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, Config.Samplerate));
|
|
|
|
|
CHECK_HR(MediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, Config.NumChannels));
|
|
|
|
|
|
|
|
|
|
CHECK_HR(Encoder->SetInputType(0, MediaType, 0));
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FWmfAudioEncoder::SetOutputType()
|
|
|
|
|
{
|
|
|
|
|
CHECK_HR(MFCreateMediaType(OutputType.GetInitReference()));
|
|
|
|
|
CHECK_HR(OutputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
|
|
|
|
|
|
|
|
|
|
if (CodecType == ECodecType::AAC)
|
|
|
|
|
{
|
|
|
|
|
CHECK_HR(OutputType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC));
|
|
|
|
|
CHECK_HR(OutputType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 16)); // the only value supported
|
|
|
|
|
CHECK_HR(OutputType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, Config.Samplerate));
|
|
|
|
|
CHECK_HR(OutputType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, Config.NumChannels));
|
|
|
|
|
CHECK_HR(OutputType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, Config.Bitrate / 8));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
checkNoEntry();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CHECK_HR(Encoder->SetOutputType(0, OutputType, 0));
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FWmfAudioEncoder::RetrieveStreamInfo()
|
|
|
|
|
{
|
|
|
|
|
CHECK_HR(Encoder->GetInputStreamInfo(0, &InputStreamInfo));
|
|
|
|
|
CHECK_HR(Encoder->GetOutputStreamInfo(0, &OutputStreamInfo));
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FWmfAudioEncoder::StartStreaming()
|
|
|
|
|
{
|
|
|
|
|
CHECK_HR(Encoder->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0));
|
|
|
|
|
CHECK_HR(Encoder->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0));
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FWmfAudioEncoder::Shutdown()
|
|
|
|
|
{
|
|
|
|
|
// Nothing to do
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FWmfAudioEncoder::Encode(const FAudioFrame& Frame)
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogAVEncoder, Verbose, TEXT("Audio input: time %.3f, duration %.3f, %d samples"), Frame.Timestamp.GetTotalSeconds(), Frame.Duration.GetTotalSeconds(), Frame.Data.GetNumSamples());
|
|
|
|
|
|
|
|
|
|
// Convert float audio data to PCM
|
|
|
|
|
PCM16 = Frame.Data;
|
|
|
|
|
|
|
|
|
|
FIMFSampleWrapper InputSample = CreateInputSample(
|
|
|
|
|
reinterpret_cast<const uint8*>(PCM16.GetData()),
|
|
|
|
|
PCM16.GetNumSamples()*sizeof(*PCM16.GetData()),
|
|
|
|
|
Frame.Timestamp,
|
|
|
|
|
Frame.Duration);
|
|
|
|
|
|
|
|
|
|
if (!InputSample.IsValid())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CHECK_HR_VOID(Encoder->ProcessInput(0, InputSample.GetSample(), 0));
|
|
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
FIMFSampleWrapper OutputSample = GetOutputSample();
|
|
|
|
|
if (!OutputSample.IsValid())
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DWORD OutputSize;
|
|
|
|
|
CHECK_HR_VOID(OutputSample.GetSample()->GetTotalLength(&OutputSize));
|
|
|
|
|
|
|
|
|
|
UE_LOG(LogAVEncoder, Verbose, TEXT("Audio encoded: time %.3f, duration %.3f, size %d"), OutputSample.GetTime().GetTotalSeconds(), OutputSample.GetDuration().GetTotalSeconds(), OutputSize);
|
|
|
|
|
|
2021-04-29 19:32:06 -04:00
|
|
|
FMediaPacket Packet(EPacketType::Audio);
|
2019-11-26 17:28:51 -05:00
|
|
|
Packet.Timestamp = OutputSample.GetTime();
|
|
|
|
|
Packet.Duration = OutputSample.GetDuration();
|
|
|
|
|
OutputSample.IterateBuffers([&Packet](int BufferIdx, TArrayView<uint8> Data)
|
|
|
|
|
{
|
|
|
|
|
Packet.Data.Append(Data.GetData(), Data.Num());
|
|
|
|
|
return false; // Terminate, because we only want 1 buffer
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
OnEncodedAudioFrame(Packet);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIMFSampleWrapper FWmfAudioEncoder::CreateInputSample(const uint8* SampleData, uint32 Size, FTimespan Timestamp, FTimespan Duration)
|
|
|
|
|
{
|
|
|
|
|
FIMFSampleWrapper Sample = { EPacketType::Audio };
|
|
|
|
|
|
|
|
|
|
if (!Sample.CreateSample())
|
|
|
|
|
{
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int32 BufferSize = FMath::Max<int32>(InputStreamInfo.cbSize, Size);
|
|
|
|
|
uint32 Alignment = InputStreamInfo.cbAlignment > 1 ? InputStreamInfo.cbAlignment - 1 : 0;
|
|
|
|
|
TRefCountPtr<IMFMediaBuffer> WmfBuffer;
|
|
|
|
|
CHECK_HR_DEFAULT(MFCreateAlignedMemoryBuffer(BufferSize, Alignment, WmfBuffer.GetInitReference()));
|
|
|
|
|
|
|
|
|
|
uint8* Dst = nullptr;
|
|
|
|
|
CHECK_HR_DEFAULT(WmfBuffer->Lock(&Dst, nullptr, nullptr));
|
|
|
|
|
FMemory::Memcpy(Dst, SampleData, Size);
|
|
|
|
|
CHECK_HR_DEFAULT(WmfBuffer->Unlock());
|
|
|
|
|
|
|
|
|
|
CHECK_HR_DEFAULT(WmfBuffer->SetCurrentLength(Size));
|
|
|
|
|
|
|
|
|
|
CHECK_HR_DEFAULT(Sample.GetSample()->AddBuffer(WmfBuffer));
|
|
|
|
|
Sample.SetTime(Timestamp);
|
|
|
|
|
Sample.SetDuration(Duration);
|
|
|
|
|
|
|
|
|
|
return Sample;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FIMFSampleWrapper FWmfAudioEncoder::GetOutputSample()
|
|
|
|
|
{
|
|
|
|
|
bool bFlag1 = OutputStreamInfo.dwFlags&MFT_OUTPUT_STREAM_PROVIDES_SAMPLES;
|
|
|
|
|
bool bFlag2 = OutputStreamInfo.dwFlags&MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES;
|
|
|
|
|
|
|
|
|
|
// Right now, we are always creating our samples, so make sure MFT will be ok with that
|
|
|
|
|
verify((bFlag1 == false && bFlag2 == false) || (bFlag1 == false && bFlag2 == true));
|
|
|
|
|
|
|
|
|
|
FIMFSampleWrapper Sample{ EPacketType::Audio };
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Create output sample
|
|
|
|
|
//
|
|
|
|
|
{
|
|
|
|
|
if (!Sample.CreateSample())
|
|
|
|
|
{
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRefCountPtr<IMFMediaBuffer> Buffer = nullptr;
|
|
|
|
|
uint32 Alignment = OutputStreamInfo.cbAlignment > 1 ? OutputStreamInfo.cbAlignment - 1 : 0;
|
|
|
|
|
CHECK_HR_DEFAULT(MFCreateAlignedMemoryBuffer(OutputStreamInfo.cbSize, Alignment, Buffer.GetInitReference()));
|
|
|
|
|
CHECK_HR_DEFAULT(Sample.GetSample()->AddBuffer(Buffer));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MFT_OUTPUT_DATA_BUFFER Output = {};
|
|
|
|
|
Output.pSample = Sample.GetSample();
|
|
|
|
|
|
|
|
|
|
DWORD Status = 0;
|
|
|
|
|
HRESULT Res = Encoder->ProcessOutput(0, 1, &Output, &Status);
|
|
|
|
|
TRefCountPtr<IMFCollection> Events = Output.pEvents; // unsure this is released
|
|
|
|
|
if (Res == MF_E_TRANSFORM_NEED_MORE_INPUT || !Sample.IsValid())
|
|
|
|
|
{
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
else if (Res == MF_E_TRANSFORM_STREAM_CHANGE)
|
|
|
|
|
{
|
|
|
|
|
if (!SetOutputType())
|
|
|
|
|
{
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GetOutputSample();
|
|
|
|
|
}
|
|
|
|
|
else if (SUCCEEDED(Res))
|
|
|
|
|
{
|
|
|
|
|
return Sample;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-29 19:32:06 -04:00
|
|
|
FAudioConfig FWmfAudioEncoder::GetConfig() const
|
2019-11-26 17:28:51 -05:00
|
|
|
{
|
|
|
|
|
return Config;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
// FWmfAudioEncoderFactory implementation
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
FWmfAudioEncoderFactory::FWmfAudioEncoderFactory()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FWmfAudioEncoderFactory::~FWmfAudioEncoderFactory()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const TCHAR* FWmfAudioEncoderFactory::GetName() const
|
|
|
|
|
{
|
|
|
|
|
return TEXT("wmf");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TArray<FString> FWmfAudioEncoderFactory::GetSupportedCodecs() const
|
|
|
|
|
{
|
|
|
|
|
TArray<FString> Codecs;
|
|
|
|
|
Codecs.Add(TEXT("aac"));
|
|
|
|
|
return Codecs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TUniquePtr<FAudioEncoder> FWmfAudioEncoderFactory::CreateEncoder(const FString& Codec)
|
|
|
|
|
{
|
2021-04-29 19:32:06 -04:00
|
|
|
if (Codec == "aac")
|
2019-11-26 17:28:51 -05:00
|
|
|
{
|
|
|
|
|
return TUniquePtr<FAudioEncoder>(new FWmfAudioEncoder(FWmfAudioEncoder::ECodecType::AAC));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UE_LOG(LogAVEncoder, Error, TEXT("FWmfAudioEncoderFactory doesn't support the %s codec"), *Codec);
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-29 19:32:06 -04:00
|
|
|
#endif //PLATFORM_WINDOWS
|
2019-11-28 08:44:18 -05:00
|
|
|
|