123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- #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;
- }
|