From 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 24 Sep 2024 14:54:57 +0200 Subject: Initial community commit --- Src/Plugins/Encoder/enc_wma/AudioCoderWMA.cpp | 468 ++++++++++++++++++++++++++ 1 file changed, 468 insertions(+) create mode 100644 Src/Plugins/Encoder/enc_wma/AudioCoderWMA.cpp (limited to 'Src/Plugins/Encoder/enc_wma/AudioCoderWMA.cpp') diff --git a/Src/Plugins/Encoder/enc_wma/AudioCoderWMA.cpp b/Src/Plugins/Encoder/enc_wma/AudioCoderWMA.cpp new file mode 100644 index 00000000..3fa7a7f0 --- /dev/null +++ b/Src/Plugins/Encoder/enc_wma/AudioCoderWMA.cpp @@ -0,0 +1,468 @@ +#include +#include +#include +#include "../nu/ns_wc.h" +#include "resource.h" +#include "wmsdk.h" // for IWMWriterSink + +#include "AudioCoderWMA.h" + +#include +#include + +#include "../nu/AutoLock.h" +#include "../nu/AutoWide.h" +#include "../Winamp/strutil.h" +#include "../Agave/Language/api_language.h" + +/* TODO: implement 2-pass encoding via IWMWriterPreprocess */ + +int config_bitrate, config_samplerate, config_nch; +// New globals for encoder query + +class CustomIndexStatus : public IWMStatusCallback +{ +public: + CustomIndexStatus( HANDLE _done ) : done(_done), IWMStatusCallback(), refcount(1) + {} + + // IUnknown methods +public: + virtual ULONG STDMETHODCALLTYPE AddRef() + { + return ++refcount; + } + + + virtual ULONG STDMETHODCALLTYPE Release() + { + // If we go to zero, who cares? + return --refcount; + } + + + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject) + { + HRESULT hRetval = E_NOINTERFACE; + if (IID_IWMStatusCallback == iid) + { + *ppvObject = static_cast(this); + hRetval = S_OK; + } + else + { + *ppvObject = NULL; + } + return hRetval; + } + + // IWMStatusCallback methods +public: + HRESULT STDMETHODCALLTYPE OnStatus( WMT_STATUS Status, HRESULT hr, WMT_ATTR_DATATYPE dwType, BYTE* pValue, void* pvContext ) + { + switch ( Status ) + { + case WMT_CLOSED: + // You may want to deal with the HRESULT value passed with the status. + // If you do, you should do it here. + + // Signal the event. + SetEvent(done); + break; + } + return S_OK; + } + +protected: + ULONG refcount; + HANDLE done; +}; + + +// Our custom buffer object, used by the writer sink. + +AudioCoderWMA::AudioCoderWMA(int numchannels, int samplerate, int bitspersamp, configtype *cfg, char *configfile) : AudioCoder() +{ + lastByteCount=0; + writerAdvanced=0; + begin_writing = false; + error = WMA_NO_ERROR; + sink = NULL; + + // Get globals from Winamp.ini config file + config_bitrate = cfg->config_bitrate; + config_samplerate = cfg->config_samplesSec; + config_nch = cfg->config_nch; + + timeunits_per_byte = ( ( (10000000.0) / (double)samplerate ) / (double)numchannels ) / ( (double)bitspersamp / 8.0 ); + //char t[100] = {0}; + //wsprintf(t,"%d", timeunits_per_byte); + //::MessageBox(NULL, t, t, MB_OK); + input_bytecount = 0; + + HRESULT hr = CreateAndConfigureWriter(numchannels, samplerate, bitspersamp, configfile); + + if ( FAILED(hr) ) + { + error = WMA_CANT_CREATE_WRITER; + } +} + +AudioCoderWMA::~AudioCoderWMA() +{ + if (writer) + { + if ( begin_writing ) + { + begin_writing = false; + writer->EndWriting(); + } + writer->Release(); + writer = NULL; + } + if (writerAdvanced) + { + writerAdvanced->Release(); + writerAdvanced=0; + } + if (sink) + { + sink->Release(); + sink=0; + } +} + +int AudioCoderWMA::GetLastError() +{ + return error; +} + +void AudioCoderWMA::PrepareToFinish() +{ + // We don't want to kill the objects here, because there might still be data in the pipeline. + if (writer && begin_writing) + { + begin_writing = false; + // Tell WM that we're done giving it input data. + + writer->EndWriting(); + // TODO: do we have to wait for this to finish? + } +} + +void AudioCoderWMA::OnFinished(const wchar_t *wfname) +{ + // + // Okay, here we need to go back and index the file we just wrote so it's seekable. + // + + // From: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmform/htm/toconfiguretheindexer.asp + // And: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmform/htm/toindexanasffile.asp + IWMIndexer* pBaseIndexer = NULL; + IWMIndexer2* pMyIndexer = NULL; + + // Create an indexer. + WMCreateIndexer(&pBaseIndexer); + + // Retrieve an IWMIndexer2 interface pointer for the indexer just created. + pBaseIndexer->QueryInterface(IID_IWMIndexer2, (void **) & pMyIndexer); + + // Release the base indexer. + pBaseIndexer->Release(); + pBaseIndexer = NULL; + + // Configure the indexer to create a timecode-based index. + pMyIndexer->Configure(0, // Stream Number, use all. + WMT_IT_PRESENTATION_TIME, // Indexer type. + NULL, // Index interval, use default. + NULL); // Index type, use default. + + // Create an event for asynchronous calls. + HANDLE done = CreateEvent(NULL, TRUE, FALSE, NULL); + + // Give that to the status object + CustomIndexStatus status( done ); + + // Start the indexer. + pMyIndexer->StartIndexing(tempFilename, &status, NULL); + + // Wait for the indexer to finish. + WaitForSingleObject(done, INFINITE); + + // Release the remaining interface. + pMyIndexer->Release(); + pMyIndexer = NULL; + + // Cleanup + CloseHandle( done ); + DeleteFileW(wfname); + MoveFileW(tempFilename, wfname); + +} + +int AudioCoderWMA::Encode(int framepos, void *in, int in_avail, int *in_used, void *out, int out_avail) +{ + HRESULT hr = S_OK; + int retval = 0; // number of bytes written into "out" + *in_used = 0; // number of bytes read from "in" + if ( !framepos && !in_avail ) + { + int x = 0; + x++; + } + assert(writer); + + // Hopefully, at the end of the stream, we still get called with "Encode" until we return 0? + if (in_avail) + { + // Allocate an INSSBuffer of the proper size to hold all the data. + INSSBuffer* pSample = NULL; + if (FAILED(writer->AllocateSample(in_avail, &pSample))) return -1; + + // Get its internal memory buffer + DWORD newBufferLength; + pSample->GetLength(&newBufferLength); + assert(newBufferLength == in_avail); + + BYTE *pdwBuffer = NULL; + if (FAILED(pSample->GetBuffer(&pdwBuffer))) return -1; + + memcpy(pdwBuffer, in, in_avail); // Send all the available bytes in the input buffer into the IWMWriter, + + pSample->SetLength(in_avail); // Tell the buffer object how much we used + + QWORD timeunits = (QWORD)( (double)input_bytecount * timeunits_per_byte ); // Compute the current timecode + // And stuff it into the writer + hr = writer->WriteSample(0, timeunits, 0, pSample); + if (FAILED(hr)) + { + } + else + { + // Increment the bytecount to be able to calculate the next timecode + input_bytecount += in_avail; + // And tell the host we used up all the available input data. + *in_used = in_avail; + } + // Release immediately + pSample->Release(); + } + + WM_WRITER_STATISTICS stats; + writerAdvanced->GetStatistics(0, &stats); + retval = (int)(stats.qwByteCount - lastByteCount); + retval = min(retval, out_avail); + lastByteCount+=retval; + memset(out, 0, retval); // so we don't write random memory to disk + + return retval; +} + +HRESULT AudioCoderWMA::SelectAndLoadResampler(int numchannels, int samplerate, int bitspersamp) +{ + DWORD inCount = 0; + BOOL success = false; + + //wsprintf(junk,"IN Chan=%d, SRate=%d, BPS=%d", numchannels, samplerate,bitspersamp); + //MessageBox(NULL, junk, "INPUT FMT", MB_OK); + // First get the number of input streams + HRESULT hr = writer->GetInputCount(&inCount); + if(!FAILED(hr)){ + //wsprintf(junk, "Input Count = %d", inCount); + //MessageBox(NULL, junk, "DEBUG", MB_OK); + // Now get the number of input formats we can resample for + DWORD fmtCount = 0; + hr = writer->GetInputFormatCount(0, &fmtCount); + if(!FAILED(hr)){ + //wsprintf(junk, "Format Count = %d", fmtCount); + //MessageBox(NULL, junk, "DEBUG", MB_OK); + // Now cycle through and find the one that matches our input fmt + for(size_t i = 0;i < fmtCount;i++){ + IWMInputMediaProps* pProps = NULL; + hr = writer->GetInputFormat(0, (DWORD)i, &pProps); + if(!FAILED(hr)){ + DWORD cbSize = 0; + // Get the size of the media type structure. + pProps->GetMediaType(NULL, &cbSize); + // Allocate memory for the media type structure. + WM_MEDIA_TYPE* pType = (WM_MEDIA_TYPE*) new BYTE[cbSize]; + if(pType != NULL){ + WAVEFORMATEX* pwave = NULL; + // Get the media type structure. + hr = pProps->GetMediaType(pType, &cbSize); + // Check that the format data is present. + if (pType->cbFormat >= sizeof(WAVEFORMATEX)){ + pwave = (WAVEFORMATEX*)pType->pbFormat; + //wsprintf(junk, "Cnannels = %d, SPerSec = %d, AvgBPS = %d, BPS = %d BALIGN = %d", + // pwave->nChannels, + // pwave->nSamplesPerSec, + // pwave->nAvgBytesPerSec, + // pwave->wBitsPerSample, + // pwave->nBlockAlign); + //MessageBox(NULL, junk, "DEBUG", MB_OK); + } + else{ + break; + } + // Try to match the channels/samplerate/and bits/samp + if((pwave->nChannels == numchannels) && (pwave->nSamplesPerSec == samplerate) && (pwave->wBitsPerSample == bitspersamp)){ + writer->SetInputProps(0, pProps); + success = true; + break; + } + } + } + } + } + } + if(success != 1){ + wchar_t junk[FILETITLE_SIZE] = {0}; + wsprintfW(junk,WASABI_API_LNGSTRINGW(IDS_CANNOT_FIND_INPUT_FORMATTER), + numchannels, samplerate,bitspersamp); + MessageBoxW(NULL, junk, WASABI_API_LNGSTRINGW(IDS_WARNING), MB_OK); + } + if (success) + return S_OK; + else if (FAILED(hr)) // if we have an error code, return it + return hr; + else + return E_FAIL; +} + + +HRESULT AudioCoderWMA::CreateAndConfigureWriter(WORD numchannels, WORD samplerate, WORD bitspersamp, char *configfile) +{ + // First, create the writer. + HRESULT hr = WMCreateWriter( NULL, &writer ); + if ( !FAILED(hr) ) + { + // Create and Configure a stream profile with the given wave limits. + WAVEFORMATEX WaveLimits = + { + WAVE_FORMAT_PCM, + numchannels, + samplerate, + samplerate * numchannels * bitspersamp / (DWORD)8, + numchannels * bitspersamp / (DWORD)8, + bitspersamp, + 0 + }; + IWMProfile* pProfile = NULL; + hr = CreateAndConfigureProfile(&WaveLimits, &pProfile, configfile); + if ( !FAILED(hr) ) + { + // Set the profile into the writer + hr = writer->SetProfile( pProfile ); + if ( !FAILED(hr) ) + { + // Go get the input resampler and load it to the profile + hr = SelectAndLoadResampler(numchannels, samplerate, bitspersamp); + if (!FAILED(hr)) + { + wchar_t tempPath[MAX_PATH] = {0}; + GetTempPathW(MAX_PATH,tempPath); + GetTempFileNameW(tempPath, L"wma", 0, tempFilename); + + // Make the custom data sink object + WMCreateWriterFileSink(&sink); + //sink = new CustomWMWriterSink; + if ( sink ) + { + sink->Open(tempFilename); + HRESULT hr; + // From MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmform/htm/addingsinkstothewriter.asp + IWMWriterSink* pSinkBase = NULL; + hr = writer->QueryInterface( IID_IWMWriterAdvanced, (void **) & writerAdvanced ); + if ( !FAILED(hr) ) + { + hr = sink->QueryInterface( IID_IWMWriterSink, (void**) & pSinkBase ); + if ( !FAILED(hr) ) + { + // Stuff the custom data sink into the writer. + hr = writerAdvanced->AddSink(pSinkBase); + if ( !FAILED(hr) ) + { + // And let the writer initialize itself for output. + hr = writer->BeginWriting(); + if ( !FAILED(hr) ) + { + begin_writing = true; + } + } + else + { + error = WMA_CANT_ADD_SINK; + } + } + else + { + error = WMA_CANT_QUERY_SINK_INTERFACE; + } + } + else + { + error = WMA_CANT_QUERY_WRITER_INTERFACE; + } + } + else + { + error = WMA_CANT_MAKE_CUSTOM_SINK; + } + } + } + } + } + + return hr; +} + +HRESULT AudioCoderWMA::CreateAndConfigureProfile(WAVEFORMATEX* pWaveLimits, IWMProfile** ppProfile, char *configfile) +{ + IWMProfileManager* pProfileMgr = NULL; + + // Instantiate a profile manager object. + HRESULT hr = WMCreateProfileManager(&pProfileMgr); + if ( !FAILED(hr) ) + { + /* SAVE + // Create the empty profile. + //hr = pProfileMgr->CreateEmptyProfile(WMT_VER_9_0, ppProfile); + if ( !FAILED(hr) ){ + IWMCodecInfo3 *codecInfo = NULL; + hr = pProfileMgr->QueryInterface(&codecInfo); + if(!FAILED(hr)){ + // Find the proper IWMStreamConfig that matches the WAVEFORMATEX data. + IWMStreamConfig* pStreamConfig = NULL; + //hr = FindAudioFormat(WMMEDIASUBTYPE_WMAudioV2, pProfileMgr, pWaveLimits, config_bitrate * 1000, FALSE, &pStreamConfig); + hr = codecInfo->GetCodecFormat(WMMEDIATYPE_Audio, config_encOffset, config_formatOffset, &pStreamConfig); + if ( !FAILED(hr) ){ + // Config the stream. + // hr = pStreamConfig->SetBitrate( config_bitrate ); + hr = pStreamConfig->SetConnectionName( L"enc_wma" ); + hr = pStreamConfig->SetStreamName( L"enc_wma" ); + hr = pStreamConfig->SetStreamNumber( 1 ); + + // Stuff it into the profile + hr = (*ppProfile)->AddStream( pStreamConfig ); + } + } + } + */ + if ( !FAILED(hr) ){ + // Load the .prx file into the writer + if(configfile == NULL){ + hr = E_FAIL; + } + else{ + wchar_t cstring[4000] = {0}; + GetPrivateProfileStructW(L"audio_wma", L"profile", cstring, sizeof(cstring)/sizeof(*cstring), AutoWide(configfile)); + hr = pProfileMgr->LoadProfileByData(cstring, ppProfile); + if(hr != S_OK){ + hr = E_FAIL; + } + } + } + pProfileMgr->Release(); + } + return hr; +} \ No newline at end of file -- cgit