diff --git a/README b/README index e13438b..edf8741 100644 --- a/README +++ b/README @@ -23,13 +23,6 @@ Linux (Ubuntu 12.x onward, Raspbian) sudo apt-get update sudo apt-get install gcc-4.8 g++-4.8 -# set up gcc alternates; we need gcc4.8 - -sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.6 20 -sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 50 -sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.6 20 -sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 50 - # install audio and UI dependencies sudo apt-get install gtk+-3-dev libnotify-dev notify-osd libasound2-dev @@ -48,9 +41,9 @@ DEBUG=1 make ubuntu // debug build or DEBUG=1 make raspbian // debug build or -make ubuntu-install // build and install locally +sudo make ubuntu-install // build and install locally or -make raspbian-install // build and install locally +sudo make raspbian-install // build and install locally # generate a debian package for distribution ./GeneratePkg.pl --platform=raspbian --application=openhome-player --version=0.1.2.3 diff --git a/Win32/IMFCodec.cpp b/Win32/IMFCodec.cpp index 4aa27a1..fd7717a 100644 --- a/Win32/IMFCodec.cpp +++ b/Win32/IMFCodec.cpp @@ -31,7 +31,7 @@ #include #define DBUG_F(...) \ - Log::Print("[CodecIMF] [%llu] ", \ + Log::Print("[%llu] [CodecIMF] ", \ std::chrono::high_resolution_clock::now().time_since_epoch().count()); \ Log::Print(__VA_ARGS__) #else @@ -90,7 +90,6 @@ class CodecIMF : public CodecBase const TChar *iStreamFormat; TBool iStreamStart; TBool iStreamEnded; - TUint64 iByteTotal; OHPlayerByteStream *iByteStream; IMFSourceReader *iSourceReader; @@ -190,7 +189,6 @@ CodecIMF::CodecIMF(IMimeTypeList& aMimeTypeList) , iStreamFormat(NULL) , iStreamStart(false) , iStreamEnded(false) - , iByteTotal(0) , iByteStream(NULL) , iSourceReader(NULL) { @@ -292,7 +290,7 @@ TBool CodecIMF::VerifyStreamType(IMFSourceReader *aSourceReader) { HRESULT hr = S_OK; IMFMediaType *mediaType = NULL; - TBool retVal = FALSE; + TBool retVal = false; hr = aSourceReader->GetNativeMediaType( (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, @@ -481,12 +479,13 @@ TBool CodecIMF::ConfigureAudioStream(IMFSourceReader *aSourceReader) SafeRelease(&uncompressedAudioType); SafeRelease(&partialType); - return FALSE; + return false; } TBool CodecIMF::Recognise(const EncodedStreamInfo& aStreamInfo) { HRESULT hr; + TBool retVal = false; #ifdef _DEBUG DBUG_F("Recognise\n"); @@ -494,13 +493,13 @@ TBool CodecIMF::Recognise(const EncodedStreamInfo& aStreamInfo) if (aStreamInfo.RawPcm()) { - return false; + return retVal; } // Initialise the byte stream. iByteStream = new OHPlayerByteStream(iController, - (BOOL *)&iStreamStart, - (BOOL *)&iStreamEnded); + (TBool *)&iStreamStart, + (TBool *)&iStreamEnded); // Create the SourceReader #ifdef _DEBUG @@ -562,7 +561,7 @@ void CodecIMF::StreamInitialise() // Remove the recognition cache and start operating on the actual stream. if (iStreamFormat == kFmtMp3) { - // Move the stream postition to that reached in Recognise(). + // Move the stream position to that reached in Recognise(). iByteStream->DisableRecogCache(true); } else @@ -758,22 +757,17 @@ void CodecIMF::Process() NULL, &sampleBuf); - if (FAILED(hr)) - { - DBUG_F("ReadSample Failed\n"); - } - if (iStreamStart) { DBUG_F("SourceReader ReadSample CodecStreamStart\n"); - FlushPCM(); + THROW(CodecStreamStart); } if (iStreamEnded) { DBUG_F("SourceReader ReadSample CodecStreamEnded\n"); - FlushPCM(); + THROW(CodecStreamEnded); } @@ -793,6 +787,14 @@ void CodecIMF::Process() THROW(CodecStreamEnded); } + if (FAILED(hr)) + { + DBUG_F("ReadSample Failed\n"); + + DBUG_F("SourceReader ReadSample CatchAll\n"); + THROW(CodecStreamEnded); + } + if (sampleBuf == NULL) { DBUG_F("No sample read\n"); diff --git a/Win32/OHPlayerByteStream.cpp b/Win32/OHPlayerByteStream.cpp index a171704..3f8e831 100644 --- a/Win32/OHPlayerByteStream.cpp +++ b/Win32/OHPlayerByteStream.cpp @@ -111,14 +111,14 @@ STDMETHODIMP ReadRequest::QueryInterface(REFIID aIId, void **aInterface) } OHPlayerByteStream::OHPlayerByteStream(ICodecController *controller, - BOOL *streamStart, - BOOL *streamEnded) : + TBool *streamStart, + TBool *streamEnded) : iStreamLength(0), iStreamPos(0), - iInAsyncRead(FALSE), - iIsRecogPhase(TRUE), - iRecogSeekOutwithCache(FALSE), - iSeekExpected(FALSE), + iInAsyncRead(false), + iIsRecogPhase(true), + iRecogCachePos(0), + iSeekExpected(false), iController(controller), iStreamStart(streamStart), iStreamEnded(streamEnded) @@ -132,7 +132,20 @@ OHPlayerByteStream::OHPlayerByteStream(ICodecController *controller, // Use a cache during stream format recognition to minimise seeking about // the physical stream. - iController->Read(iRecogCache, iRecogCache.MaxBytes()); + iRecogCache.SetBytes(0); + try + { + iController->Read(iRecogCache, iRecogCache.MaxBytes()); + +#ifdef _DEBUG + DBUG_F("OHPlayerByteStream: Recognition Cache [%lu]\n", + iRecogCache.Bytes()); +#endif + } + catch (...) + { + DBUG_F("OHPlayerByteStream: Unexpected recognition cache error\n"); + } } OHPlayerByteStream::~OHPlayerByteStream() @@ -144,7 +157,7 @@ OHPlayerByteStream::~OHPlayerByteStream() _aligned_free(iRefCount); } -// Note the stream fomat recognition phase is complete. +// Note the stream format recognition phase is complete. // // This effects how Seek() requests are processed. void OHPlayerByteStream::RecognitionComplete() @@ -153,13 +166,13 @@ void OHPlayerByteStream::RecognitionComplete() DBUG_F("RecognitionComplete\n"); #endif - iIsRecogPhase = FALSE; + iIsRecogPhase = false; } // Release the recognition cache. // // Future operations will be performed on the physical stream. -void OHPlayerByteStream::DisableRecogCache(BOOL revertStreamPos) +void OHPlayerByteStream::DisableRecogCache(TBool revertStreamPos) { #ifdef _DEBUG DBUG_F("DisableRecogCache\n"); @@ -203,6 +216,11 @@ void OHPlayerByteStream::DisableRecogCache(BOOL revertStreamPos) break; } } + +#ifdef _DEBUG + DBUG_F("DisableRecogCache - Stream Advanced To [%llu]\n", + iController->StreamPos()); +#endif } else { @@ -211,6 +229,7 @@ void OHPlayerByteStream::DisableRecogCache(BOOL revertStreamPos) // Disable the recognition cache iRecogCache.SetBytes(0); + iRecogCachePos = -1; } // Not that a stream seek is expected and should not be ignored. @@ -220,7 +239,7 @@ void OHPlayerByteStream::ExpectExternalSeek() DBUG_F("ExpectExternalSeek\n"); #endif - iSeekExpected = TRUE; + iSeekExpected = true; } // IUnknown Methods. @@ -293,10 +312,10 @@ STDMETHODIMP OHPlayerByteStream::BeginRead(BYTE *aBuffer, IMFAsyncResult *result; ReadRequest *requestState; - iInAsyncRead = TRUE; + iInAsyncRead = true; // Execute a synchronous read - hr = Read(aBuffer, aLength, &bytesRead); + Read(aBuffer, aLength, &bytesRead); // Create the result object to pass to EndRead() requestState = new ReadRequest(bytesRead); @@ -308,16 +327,16 @@ STDMETHODIMP OHPlayerByteStream::BeginRead(BYTE *aBuffer, SafeRelease(&requestState); - // Set async operation status to the result of the read. - result->SetStatus(S_OK); - if (SUCCEEDED(hr)) { + // Set async operation status to the result of the read. + result->SetStatus(S_OK); + // Invoke the async callback to instigate the EndRead() call. hr = MFInvokeCallback(result); } - return S_OK; + return hr; } STDMETHODIMP OHPlayerByteStream::Read(BYTE *aBuffer, @@ -329,23 +348,50 @@ STDMETHODIMP OHPlayerByteStream::Read(BYTE *aBuffer, // If we have a cache and the current stream position resides // within it return data from the cache. - if (iStreamPos < (LONGLONG)iRecogCache.Bytes()) + if (iIsRecogPhase) { - ULONG available = (ULONG)(iRecogCache.Bytes() - iStreamPos); + if ((iStreamPos < iRecogCachePos) || + (iStreamPos >= iRecogCachePos + iRecogCache.Bytes())) + { + // To fulfill this request we need to reallocate the cache. + iRecogCache.SetBytes(0); + try + { + // Perform an out of band read on the stream. + iController->Read(*this, iStreamPos, aLength); + + iRecogCachePos = iStreamPos; + +#ifdef _DEBUG + DBUG_F("Read: iRecogCachePos [%lld] Size [%lu]\n", + iRecogCachePos, iRecogCache.Bytes()); +#endif + } + catch (...) + { + DBUG_F("Read: Unexpected error reallocating recognition " + "cache\n"); + } + } + + LONGLONG streamPos = iStreamPos; + + ULONG cacheByteOffset = (ULONG)(streamPos - iRecogCachePos); + ULONG available = (ULONG)(iRecogCache.Bytes() - cacheByteOffset); + +#ifdef _DEBUG + DBUG_F("Read: Cache Req [%lu] Avail [%lu]\n", aLength, available); +#endif if (aLength > available) { aLength = available; } - memcpy(aBuffer, (char *)(iRecogCache.Ptr() + iStreamPos), aLength); + memcpy(aBuffer, (char *)(iRecogCache.Ptr() + cacheByteOffset), aLength); *aBytesRead = aLength; iStreamPos += *aBytesRead; - -#ifdef _DEBUG - DBUG_F("Read: Cache [%lu]\n", *aBytesRead); -#endif } else { @@ -355,8 +401,18 @@ STDMETHODIMP OHPlayerByteStream::Read(BYTE *aBuffer, inputBuffer.SetBytes(0); - // Read the requested amount of data from the physical stream. - iController->Read(inputBuffer, inputBuffer.MaxBytes()); + if (*iStreamEnded || *iStreamStart) + { +#ifdef _DEBUG + DBUG_F("Read: Pre-existing exception [%d] [%d]\n", + *iStreamStart, *iStreamEnded); +#endif + } + else + { + // Read the requested amount of data from the physical stream. + iController->Read(inputBuffer, inputBuffer.MaxBytes()); + } *aBytesRead = (ULONG)inputBuffer.Bytes(); iStreamPos += *aBytesRead; @@ -364,31 +420,36 @@ STDMETHODIMP OHPlayerByteStream::Read(BYTE *aBuffer, catch(CodecStreamStart&) { #ifdef _DEBUG - DBUG_F("Read: CodecStreamStart Exception Caught\n"); + DBUG_F("Read: CodecStreamStart Exception Caught. Bytes[%lu]\n", + *aBytesRead); #endif // _DEBUG - *iStreamStart = TRUE; + *iStreamStart = true; return S_OK; } catch(CodecStreamEnded&) { #ifdef _DEBUG - DBUG_F("Read: CodecStreamEnded Exception Caught\n"); + DBUG_F("Read: CodecStreamEnded Exception Caught. Bytes[%lu]\n", + *aBytesRead); #endif // _DEBUG - *iStreamEnded = TRUE; + *iStreamEnded = true; return S_OK; } catch(CodecStreamStopped&) { #ifdef _DEBUG - DBUG_F("Read: CodecStreamStopped Exception Caught\n"); + DBUG_F("Read: CodecStreamStopped Exception Caught. Bytes[%lu]\n", + *aBytesRead); #endif // _DEBUG - *iStreamEnded = TRUE; + *iStreamEnded = true; return S_OK; } } +#ifdef _DEBUG DBUG_F("Read: Req[%lu] Got[%lu] Pos[%llu]\n", aLength, *aBytesRead, iStreamPos); +#endif return S_OK; } @@ -414,12 +475,17 @@ STDMETHODIMP OHPlayerByteStream::EndRead(IMFAsyncResult *aResult, SafeRelease(&requestState); - iInAsyncRead = FALSE; + iInAsyncRead = false; hr = aResult->GetStatus(); SafeRelease(&aResult); + if (FAILED(hr)) + { + DBUG_F("EndRead Returning Fail\n"); + } + return hr; } @@ -440,73 +506,30 @@ STDMETHODIMP OHPlayerByteStream::Seek(MFBYTESTREAM_SEEK_ORIGIN aSeekOrigin, switch (aSeekOrigin) { case msoBegin: - DBUG_F("Seek Origin: Offset [%llu]\n", aSeekOffset); + DBUG_F("Seek Origin: Offset [%lld]\n", aSeekOffset); break; case msoCurrent: - DBUG_F("Seek Curent: Offset [%llu]\n", aSeekOffset); + DBUG_F("Seek Current: Offset [%lld]\n", aSeekOffset); aSeekOffset += iStreamPos; break; } - if (aSeekOffset >= iRecogCache.Bytes()) + if (aSeekOffset < iRecogCachePos || + aSeekOffset >= iRecogCachePos + iRecogCache.Bytes()) { // A seek on the physical stream is required. - // - // Unfortunately the SourceReader has the propensity to, on occasion, - // execute seeks backwards from the current stream position then - // forwards again to the current position when decoding the stream. - // - // For ease of integration the seeks are ignored. - if (iIsRecogPhase || iRecogSeekOutwithCache || iSeekExpected) + if (iIsRecogPhase) { -#if 0 - infile.clear(); - - if (infile.seekg(aSeekOffset, way)) - { - // If the recognition cache is active note that we have - // seek'd outwith it. - // - // A physical seek will be required to prior to decoding to - // get back to the start of the stream. - // - // This will not be required in LitePipe as we are automatically - // returned to the start of the stream after recognition - // is complete. - if (iRecogCache.Bytes() > 0) - { - iRecogSeekOutwithCache = TRUE; - } - - *aCurrentPosition = aSeekOffset; - - DBUG_F("[%llu]:", *aCurrentPosition); - DBUG_F("Success [Physical]\n"); - - // We don't track physical seeks after recognition. - // - // Not required in LitePipe. - if (!iIsRecogPhase) - { - iRecogSeekOutwithCache = FALSE; - } - - // This seek was instigated via LitePipe and thus allowed. - // - // Reset things so seeks will be ignored, during decoding, - // until the next LitePipe instigated seek. - if (iSeekExpected) - { - iSeekExpected = FALSE; - } - } - else - { - DBUG_F("Failure\n"); - hr = E_FAIL; - } + // During the recognition phase we fake any attempted seeks out with + // the cache. The resulting 'read' will reallocate the cache with + // data being returned from the new cache. +#ifdef _DEBUG + DBUG_F("Seek: Recognition Phase Seek Faked [%lld]\n", aSeekOffset); #endif + + *aCurrentPosition = aSeekOffset; + iStreamPos = aSeekOffset; } else { @@ -518,7 +541,9 @@ STDMETHODIMP OHPlayerByteStream::Seek(MFBYTESTREAM_SEEK_ORIGIN aSeekOrigin, // correct position for the next read. *aCurrentPosition = aSeekOffset; - DBUG_F("Seek: Codec instigated seek skipped\n"); +#ifdef _DEBUG + DBUG_F("Seek: Codec instigated seek skipped [%lld]\n", aSeekOffset); +#endif } } else @@ -528,7 +553,9 @@ STDMETHODIMP OHPlayerByteStream::Seek(MFBYTESTREAM_SEEK_ORIGIN aSeekOrigin, // Update the position to the required offset. *aCurrentPosition = aSeekOffset; - DBUG_F("Seek Success [From Cache]\n"); +#ifdef _DEBUG + DBUG_F("Seek Success [From Cache] [%lld]\n", aSeekOffset); +#endif } iStreamPos = *aCurrentPosition; @@ -563,7 +590,11 @@ STDMETHODIMP OHPlayerByteStream::SetCurrentPosition(QWORD aPosition) STDMETHODIMP OHPlayerByteStream::GetCapabilities(DWORD *aCapabilities) { // Seeking disabled initially. - *aCapabilities = MFBYTESTREAM_IS_READABLE /*| MFBYTESTREAM_IS_SEEKABLE*/; + *aCapabilities = MFBYTESTREAM_IS_READABLE | MFBYTESTREAM_IS_SEEKABLE; + +#ifdef _DEBUG + DBUG_F("GetCapabilities %d\n", *aCapabilities); +#endif return S_OK; } @@ -603,20 +634,20 @@ STDMETHODIMP OHPlayerByteStream::IsEndOfStream(BOOL *aIsEndOfStream) if (iStreamPos >= iStreamLength) { #ifdef _DEBUG - DBUG_F("IsEndOfStream [%llu] [%llu] [TRUE]\n", + DBUG_F("IsEndOfStream [%lld] [%lld] [TRUE]\n", iStreamPos, iStreamLength); #endif - *aIsEndOfStream = TRUE; + *aIsEndOfStream = true; } else { #ifdef _DEBUG - DBUG_F("IsEndOfStream [%llu] [%llu] [FALSE]\n", + DBUG_F("IsEndOfStream [%lld] [%lld] [false]\n", iStreamPos, iStreamLength); #endif - *aIsEndOfStream = FALSE; + *aIsEndOfStream = false; } return S_OK; @@ -681,4 +712,26 @@ STDMETHODIMP OHPlayerByteStream::Flush() return S_OK; } +// IWriter functions +void OHPlayerByteStream::Write(TByte aValue) +{ + if (! iRecogCache.TryAppend(aValue)) + { + DBUG_F("Write TByte: Failed to add to iRecogCache\n"); + } +} + +void OHPlayerByteStream::Write(const Brx& aBuffer) +{ + if (! iRecogCache.TryAppend(aBuffer)) + { + DBUG_F("Write Brx: Failed to add to iRecogCache\n"); + } +} + +void OHPlayerByteStream::WriteFlush() +{ + DBUG_F("WriteFlush\n"); +} + #endif USE_IMFCODEC diff --git a/Win32/OHPlayerByteStream.h b/Win32/OHPlayerByteStream.h index 733ab2a..294b14f 100644 --- a/Win32/OHPlayerByteStream.h +++ b/Win32/OHPlayerByteStream.h @@ -8,12 +8,13 @@ namespace OpenHome { namespace Media { namespace Codec { -class OHPlayerByteStream : public IMFByteStream +class OHPlayerByteStream : public IMFByteStream, public IWriter { +// From IMFByteStream public: OHPlayerByteStream(ICodecController *controller, - BOOL *streamStart, - BOOL *streamEnded); + TBool *streamStart, + TBool *streamEnded); ~OHPlayerByteStream(); // IUnknown Methods. @@ -64,10 +65,17 @@ class OHPlayerByteStream : public IMFByteStream STDMETHODIMP Flush(); - // Integration Extras +// From IWriter +public: + void Write(TByte aValue) override; + void Write(const Brx& aBuffer) override; + void WriteFlush() override; + +// Integration Extras +public: // Disable the stream format recognition cache. - void DisableRecogCache(BOOL revertStreamPos); + void DisableRecogCache(TBool revertStreamPos); void RecognitionComplete(); // Note the completion of format recognition. void ExpectExternalSeek(); // Act on the next seek request. @@ -80,17 +88,16 @@ class OHPlayerByteStream : public IMFByteStream ULONG *iRefCount; // Object reference count. LONGLONG iStreamLength; // Stream length LONGLONG iStreamPos; // Current stream position. - BOOL iInAsyncRead; // Currently in begin/end read + TBool iInAsyncRead; // Currently in begin/end read // sequence. - BOOL iIsRecogPhase; // Recognising stream format. - Bws iRecogCache; // Recognition cache. - BOOL iRecogSeekOutwithCache; // Seeked out with cache during - // recognition. - BOOL iSeekExpected; // Honour the next seek request. + TBool iIsRecogPhase; // Recognising stream format. + Bws iRecogCache; // Recognition cache + LONGLONG iRecogCachePos; // Stream position of recog cache. + TBool iSeekExpected; // Honour the next seek request. ICodecController *iController; // Codec Controller. - BOOL *iStreamStart; - BOOL *iStreamEnded; + TBool *iStreamStart; + TBool *iStreamEnded; }; } // namespace Codec diff --git a/Win32/OptionalFeatures.h b/Win32/OptionalFeatures.h index 6860614..50bd70c 100644 --- a/Win32/OptionalFeatures.h +++ b/Win32/OptionalFeatures.h @@ -1,3 +1,8 @@ +#define ENABLE_AAC +#define ENABLE_MP3 +#define ENABLE_TIDAL +#define ENABLE_QOBUZ +#define ENABLE_RADIO // Optional Features // Uncomment this line to enable MP3 support @@ -8,16 +13,16 @@ // Uncomment this line to enable the Radio source //#define ENABLE_RADIO -#define TUNEIN_PARTNER_ID TUNEIN_PARTNER_ID_STRING +#define TUNEIN_PARTNER_ID "ah2rjr68" // Uncomment this line to enable Tidal service support //#define ENABLE_TIDAL -// Replace TIDAL_TOKEN_STRING with a valid Tidal token -#define TIDAL_TOKEN TIDAL_TOKEN_STRING +// Replace "dvmPAFPZbSv19FUp" with a valid Tidal token +#define TIDAL_TOKEN "dvmPAFPZbSv19FUp" // Uncomment this line to enable Qobuz service support //#define ENABLE_QOBUZ -// Replace QOBUZ_APPID_STRING with a valid Qobuz Application ID -#define QOBUZ_APPID QOBUZ_APPID_STRING -// Replace QOBUZ_SECRET_STRING with a valid Qobuz secret -#define QOBUZ_SECRET QOBUZ_SECRET_STRING +// Replace "854233864" with a valid Qobuz Application ID +#define QOBUZ_APPID "854233864" +// Replace "68ee8040d64ec825c5393396ca1cea1e" with a valid Qobuz secret +#define QOBUZ_SECRET "68ee8040d64ec825c5393396ca1cea1e" diff --git a/linux/Makefile.ubuntu b/linux/Makefile.ubuntu index 385f885..e7196a6 100644 --- a/linux/Makefile.ubuntu +++ b/linux/Makefile.ubuntu @@ -22,11 +22,11 @@ GTK_LIBS := $(shell pkg-config --libs gtk+-3.0) # indicate to build that we have selected a valid target HWPLATFORM=$(shell uname -i) ifeq ($(HWPLATFORM),i686) - CC = g++ + CC = g++-4.8 TARG_ARCH = Linux-x86 else ifeq ($(HWPLATFORM),x86_64) - CC = g++ + CC = g++-4.8 TARG_ARCH = Linux-x64 else $(error please build on an x86/x64 Ubunutu machine)