aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Encoder/enc_wma/AudioCoderWMA.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Encoder/enc_wma/AudioCoderWMA.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/Plugins/Encoder/enc_wma/AudioCoderWMA.cpp')
-rw-r--r--Src/Plugins/Encoder/enc_wma/AudioCoderWMA.cpp468
1 files changed, 468 insertions, 0 deletions
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 <windows.h>
+#include <mmreg.h>
+#include <msacm.h>
+#include "../nu/ns_wc.h"
+#include "resource.h"
+#include "wmsdk.h" // for IWMWriterSink
+
+#include "AudioCoderWMA.h"
+
+#include <cassert>
+#include <exception>
+
+#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<IWMStatusCallback *>(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