2019-03-13 09:42:39 -04:00
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
2019-02-27 11:57:17 -05:00
# include "WmfAudioEncoder.h"
# include "GameplayMediaEncoderSample.h"
GAMEPLAYMEDIAENCODER_START
FWmfAudioEncoder : : FWmfAudioEncoder ( const FOutputSampleCallback & OutputCallback ) :
OutputCallback ( OutputCallback )
{ }
bool FWmfAudioEncoder : : Initialize ( const FWmfAudioEncoderConfig & InConfig )
{
if ( InConfig . SampleRate ! = 44100 & & InConfig . SampleRate ! = 48000 )
{
UE_LOG ( GameplayMediaEncoder , Error , TEXT ( " AAC SampleRate must be 44100 or 48000, configured: %d. see: https://docs.microsoft.com/en-us/windows/desktop/medfound/aac-encoder " ) , InConfig . SampleRate ) ;
return false ;
}
if ( InConfig . NumChannels ! = 1 & & InConfig . NumChannels ! = 2 & & InConfig . NumChannels ! = 6 )
{
UE_LOG ( GameplayMediaEncoder , Error , TEXT ( " AAC NumChannels must be 1, 2 or 6 (5.1), configured: %d. see: https://docs.microsoft.com/en-us/windows/desktop/medfound/aac-encoder " ) , InConfig . NumChannels ) ;
return false ;
}
UE_LOG ( GameplayMediaEncoder , Log , TEXT ( " AudioEncoder config: %d channels, %d Hz, %.2f Kbps " ) , InConfig . NumChannels , InConfig . SampleRate , InConfig . Bitrate / 1000.0f ) ;
Config = InConfig ;
CHECK_HR ( CoCreateInstance ( CLSID_AACMFTEncoder , nullptr , CLSCTX_INPROC_SERVER , IID_IMFTransform , reinterpret_cast < void * * > ( & Encoder ) ) ) ;
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 ( )
{
TRefCountPtr < IMFMediaType > InputType ;
CHECK_HR ( Encoder - > GetInputCurrentType ( 0 , InputType . GetInitReference ( ) ) ) ;
CHECK_HR ( MFCreateMediaType ( OutputType . GetInitReference ( ) ) ) ;
CHECK_HR ( OutputType - > SetGUID ( MF_MT_MAJOR_TYPE , MFMediaType_Audio ) ) ;
CHECK_HR ( OutputType - > SetGUID ( MF_MT_SUBTYPE , MFAudioFormat_AAC ) ) ;
CHECK_HR ( OutputType - > SetUINT32 ( MF_MT_AUDIO_BITS_PER_SAMPLE , 16 ) ) ; // the only value supported
uint32 SampleRate ;
CHECK_HR ( InputType - > GetUINT32 ( MF_MT_AUDIO_SAMPLES_PER_SECOND , & SampleRate ) ) ;
CHECK_HR ( OutputType - > SetUINT32 ( MF_MT_AUDIO_SAMPLES_PER_SECOND , SampleRate ) ) ;
uint32 NumChannels ;
CHECK_HR ( InputType - > GetUINT32 ( MF_MT_AUDIO_NUM_CHANNELS , & NumChannels ) ) ;
CHECK_HR ( OutputType - > SetUINT32 ( MF_MT_AUDIO_NUM_CHANNELS , NumChannels ) ) ;
CHECK_HR ( OutputType - > SetUINT32 ( MF_MT_AUDIO_AVG_BYTES_PER_SECOND , Config . Bitrate ) ) ;
CHECK_HR ( Encoder - > SetOutputType ( 0 , OutputType , 0 ) ) ;
return true ;
}
bool FWmfAudioEncoder : : GetOutputType ( TRefCountPtr < IMFMediaType > & OutType )
{
OutType = OutputType ;
return OutputType . IsValid ( ) ;
}
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 ;
}
bool FWmfAudioEncoder : : Process ( const int8 * SampleData , uint32 Size , FTimespan Timestamp , FTimespan Duration )
{
UE_LOG ( GameplayMediaEncoder , Verbose , TEXT ( " Audio input: time %.3f, duration %.3f, size %d " ) , Timestamp . GetTotalSeconds ( ) , Duration . GetTotalSeconds ( ) , Size ) ;
FGameplayMediaEncoderSample InputSample { EMediaType : : Audio } ;
if ( ! CreateInputSample ( SampleData , Size , Timestamp , Duration , InputSample ) )
{
return false ;
}
CHECK_HR ( Encoder - > ProcessInput ( 0 , InputSample . GetSample ( ) , 0 ) ) ;
while ( true )
{
FGameplayMediaEncoderSample OutputSample { EMediaType : : Audio } ;
if ( ! GetOutputSample ( OutputSample ) )
{
return false ;
}
if ( ! OutputSample . IsValid ( ) )
{
break ;
}
DWORD OutputSize ;
CHECK_HR ( OutputSample . GetSample ( ) - > GetTotalLength ( & OutputSize ) ) ;
UE_LOG ( GameplayMediaEncoder , Verbose , TEXT ( " Audio encoded: time %.3f, duration %.3f, size %d " ) , OutputSample . GetTime ( ) . GetTotalSeconds ( ) , OutputSample . GetDuration ( ) . GetTotalSeconds ( ) , OutputSize ) ;
if ( ! OutputCallback ( OutputSample ) )
{
return false ;
}
}
return true ;
}
// TODO(andriy): consider pooling IMFSample allocations, same for video encoder
bool FWmfAudioEncoder : : CreateInputSample ( const int8 * SampleData , uint32 Size , FTimespan Timestamp , FTimespan Duration , FGameplayMediaEncoderSample & Sample )
{
if ( ! Sample . CreateSample ( ) )
{
return false ;
}
int32 BufferSize = FMath : : Max < int32 > ( InputStreamInfo . cbSize , Size ) ;
uint32 Alignment = InputStreamInfo . cbAlignment > 1 ? InputStreamInfo . cbAlignment - 1 : 0 ;
TRefCountPtr < IMFMediaBuffer > WmfBuffer ;
CHECK_HR ( MFCreateAlignedMemoryBuffer ( BufferSize , Alignment , WmfBuffer . GetInitReference ( ) ) ) ;
uint8 * Dst = nullptr ;
CHECK_HR ( WmfBuffer - > Lock ( & Dst , nullptr , nullptr ) ) ;
FMemory : : Memcpy ( Dst , SampleData , Size ) ;
CHECK_HR ( WmfBuffer - > Unlock ( ) ) ;
CHECK_HR ( WmfBuffer - > SetCurrentLength ( Size ) ) ;
CHECK_HR ( Sample . GetSample ( ) - > AddBuffer ( WmfBuffer ) ) ;
Sample . SetTime ( Timestamp ) ;
Sample . SetDuration ( Duration ) ;
return true ;
}
bool FWmfAudioEncoder : : CreateOutputSample ( FGameplayMediaEncoderSample & Sample )
{
if ( ! Sample . CreateSample ( ) )
{
return false ;
}
TRefCountPtr < IMFMediaBuffer > Buffer = nullptr ;
uint32 Alignment = OutputStreamInfo . cbAlignment > 1 ? OutputStreamInfo . cbAlignment - 1 : 0 ;
CHECK_HR ( MFCreateAlignedMemoryBuffer ( OutputStreamInfo . cbSize , Alignment , Buffer . GetInitReference ( ) ) ) ;
CHECK_HR ( Sample . GetSample ( ) - > AddBuffer ( Buffer ) ) ;
return true ;
}
bool FWmfAudioEncoder : : GetOutputSample ( FGameplayMediaEncoderSample & Sample )
{
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 ) ) ;
MFT_OUTPUT_DATA_BUFFER Output = { } ;
if ( ! CreateOutputSample ( Sample ) )
{
return false ;
}
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 ( ) )
{
Sample . Reset ( ) ; // do not return empty sample
return true ; // not an error, just not enough input
}
else if ( Res = = MF_E_TRANSFORM_STREAM_CHANGE )
{
if ( ! SetOutputType ( ) )
{
return false ;
}
return GetOutputSample ( Sample ) ;
}
return SUCCEEDED ( Res ) ;
}
bool FWmfAudioEncoder : : Flush ( )
{
CHECK_HR ( Encoder - > ProcessMessage ( MFT_MESSAGE_NOTIFY_END_OF_STREAM , 0 ) ) ;
CHECK_HR ( Encoder - > ProcessMessage ( MFT_MESSAGE_COMMAND_DRAIN , 0 ) ) ;
return true ;
}
GAMEPLAYMEDIAENCODER_END