Проблемы при переходе с мобильного клиента Gracenote на GNSDK для мобильных устройств

Я работаю над переносом своего приложения для Android с устаревшего мобильного клиента Gracenote на более новый GNSDK для мобильного SDK, и я столкнулся с несколькими проблемами:

  1. В мобильном клиенте я использовал GNOperations.recognizeMIDStreamFromRadio(GNSearchResultReady, GNConfig, samplePCMBuffer), чтобы инициировать отпечаток пальца и операцию поиска в буфере PCM. Мое приложение может передавать в Gracenote только предварительно записанный звук (в отличие от простого указания Gracenote на источник потокового звука), в идеале в виде необработанного PCM, хотя при необходимости я мог бы кодировать в стандартные форматы сжатия. Что мне следует использовать из GNSDK для мобильного API, чтобы выполнить ту же операцию отпечатка пальца и поиска для предоставленных предварительно записанных аудиоданных, которые, надеюсь, все еще могут быть необработанными PCM?
  2. Класс GnMusicId выглядит так, как будто он может быть удобным универсальным генератором отпечатков пальцев и классом издателя запросов, поэтому он может быть ответом на пункт 1 выше. Однако я не нашел способа определить, когда он заканчивает запись отпечатка пальца, и, следовательно, мы готовы выдать запрос. Как я могу получить обратный вызов, сообщающий мне, что GnMusicId закончил запись отпечатка пальца из метода GnMusicId.fingerprintWrite(byte[] audioData, long audioDataSize) и что отпечаток готов для использования в запросе через GnMusicId.findAlbums(fingerprintDataGet (), GnFingerprintType.kFingerprintTypeStream6)?
  3. В мобильном клиенте я смог отменить текущие операции Gracenote, используя GNOperations.cancel(GNSearchResultReady) — я читал, что новая архитектура требует, чтобы определенные операции отменялись индивидуально из-за более модульной конструкции, но я не нашел стандарта API отмены для различных операций, которые может выполнять GNSDK для мобильных устройств. Как мне отменить операции поиска отпечатков пальцев и песен в GNSDK для мобильных устройств?

person CCJ    schedule 11.11.2017    source источник


Ответы (1)


Оказывается, вы можете распознать заданный массив PCM в GNSDK для Android с помощью следующих трех вызовов API GnMusicIdStream:

  1. GnMusicIdStream.audioProcessStart(sampleRateHz,pcmBitcount,channelCount) для подготовки механизма распознавания к входящему PCM.
  2. GnMusicIdStream.audioProcess(pcmArray,pcmArray.length) для передачи массива PCM, который вы хотите распознать
  3. GnMusicIdStream.identifyAlbumAsync() для создания отпечатка пальца и последующего использования его для операции поиска. Это вызовет обратные вызовы к объекту IGnMusicIdStreamEvents, переданному вашему экземпляру GnMusicIdStream, и musicIdStreamAlbumResult() предоставит любые результаты.

Насколько я понимаю, используя этот подход, вам не нужно явно ждать генерации отпечатков пальцев и т. д. — вы просто вызываете эти три метода по порядку, а затем GNSDK обрабатывает остальные и в конечном итоге выполняет обратный вызов. Полная операция id выглядит так:

try {


        mGnMusicIdStream = new GnMusicIdStream(mGnUser, GnMusicIdStreamPreset.kPresetRadio, new IGnMusicIdStreamEvents() {
            @Override
            public void musicIdStreamProcessingStatusEvent(GnMusicIdStreamProcessingStatus gnMusicIdStreamProcessingStatus, IGnCancellable iGnCancellable) {
                Log.d(TAG,"gracenote gnsdk -- musicIdStreamProcessingStatusEvent(); event is: "+gnMusicIdStreamProcessingStatus);
            }

            @Override
            public void musicIdStreamIdentifyingStatusEvent(GnMusicIdStreamIdentifyingStatus gnMusicIdStreamIdentifyingStatus, IGnCancellable iGnCancellable) {
                Log.d(TAG,"gracenote gnsdk -- musicIdStreamIdentifyingStatusEvent(); event is: "+gnMusicIdStreamIdentifyingStatus);
            }

            @Override
            public void musicIdStreamAlbumResult(GnResponseAlbums gnResponseAlbums, IGnCancellable iGnCancellable) {

                Log.d(TAG,"gracenote gnsdk -- musicIdStreamAlbumResult();  responsealbums matches: "+gnResponseAlbums.resultCount());

                if (gnResponseAlbums.resultCount() > 0) {
                    try {
                        final GnAlbum albumResponse = gnResponseAlbums.albums().at(0).next();

                        final GnTrack trackResponse = albumResponse.trackMatched();

                        if (trackResponse != null) {
                            mEvent.postOnGNSearchResult(new ISongRecognitionResponse() {
                                @Override
                                public
                                @NonNull
                                String extractTrackTitle() {
                                    // seems that track title comes reliably from GnTrack and much of the rest is locked
                                    // up in the GnAlbum?
                                    if (trackResponse.title() != null) {
                                        return trackResponse.title().display();
                                    } else {
                                        return "";
                                    }
                                }

                                @Override
                                public
                                @NonNull
                                String extractTrackArtist() {
                                    if (albumResponse.artist() != null) {
                                        if(BuildConfig.RULE_DEBUG_LEVEL>0)
                                            Log.d(TAG,"gnsdk -- album artist says "+albumResponse.artist().name().display());
                                        return albumResponse.artist().name().display();
                                    } else {
                                        return "";
                                    }
                                }

                                @Override
                                public long extractTrackPosition() {
                                    return trackResponse.currentPosition();
                                }

                                @Override
                                public long extractTrackDuration() {
                                    return trackResponse.duration();
                                }

                                @Override
                                public byte[] extractCoverArtImageData() {
                                    // seems that base64 string of the image is not always/commonly available
                                    // at least as we're trying to access it here.  The sample app downloads the image
                                    // asynchronously from the URL, which seems more reliable
                                    String img64 = albumResponse.coverArt().asset(GnImageSize.kImageSizeSmall).imageDataBase64(); //trackResponse.content(GnContentType.kContentTypeImageCover).asset(GnImageSize.kImageSize220).imageDataBase64();

                                    if(img64 != null && !img64.isEmpty()) {
                                        return Base64.decode(img64, Base64.DEFAULT);
                                    }else{
                                        return null;
                                    }
                                }

                                @NonNull
                                @Override
                                public String extractCoverArtImageURL() {
                                    // beware: asking for specific image sizes has been known to cause
                                    // no cover art to come back even if there might be cover art at another size.
                                    // The sample app uses the categorical size qualifier constant kImageSizeSmall
                                    String httpURL = albumResponse.coverArt().asset(GnImageSize.kImageSizeSmall).urlHttp();

                                    return httpURL;
                                }
                            });
                        }//end if track response data is non-null
                        else {
                            mEvent.postOnGNSearchResult(null);
                        }
                    }catch(GnException e){
                        Log.e(TAG, "we received a response clbk, but failed to process it", e);
                    }
                }//end if greater than 0 results
                else{
                    //no results, so pass a null result to indicate a miss
                    mEvent.postOnGNSearchResult(null);
                }
            }

            @Override
            public void musicIdStreamIdentifyCompletedWithError(GnError gnError) {
                Log.e(TAG,"gnsdk -- musicIdStreamIdentifyCompletedWithError(); we received a response clbk, but failed to process it");
                mEvent.postOnGNSearchFailure(gnError.errorDescription());
            }

            @Override
            public void statusEvent(GnStatus gnStatus, long l, long l1, long l2, IGnCancellable iGnCancellable) {
                Log.e(TAG,"gnsdk -- statusEvent(); status is: "+gnStatus);
            }
        });

        //configure the options on the gnmusicidstream instance
        mGnMusicIdStream.options().lookupData(GnLookupData.kLookupDataContent, true);
        mGnMusicIdStream.options().lookupData(GnLookupData.kLookupDataSonicData, true);
        mGnMusicIdStream.options().lookupMode(GnLookupMode.kLookupModeOnline);
        mGnMusicIdStream.options().preferResultCoverart(true);
        mGnMusicIdStream.options().resultSingle(true);

        //configure audio processing params on gnmusicidstream
        mGnMusicIdStream.audioProcessStart(sampleRateHz,pcmBitcount,channelCount);

        //pass the pcm array to the gnmusicidstream for processing
        mGnMusicIdStream.audioProcess(pcmArray,pcmArray.length);

        //initiate the lookup operation based on the processed pcm
        mGnMusicIdStream.identifyAlbumAsync();


    }catch(GnException e){
        Log.e(TAG,"gnsdk -- failed recognition operation",e);
    }

Возвращаемые данные немного сбивают с толку, с несколькими потенциальными способами извлечения метаданных о дорожке, которые могут быть нулевыми или пустыми при запросе определенными способами, а не при запросе другими способами. Интересные моменты, которые я нашел об объекте ответа GnResponseAlbums до сих пор (я не уверен в договоре о допустимости значений NULL для возвращаемых значений, упомянутых ниже, поэтому следите за исключениями nullpointerexceptions):

  • gnResponseAlbums.resultCount() будет равно 0, если явно ничего не пошло не так, но совпадений не найдено.

  • совпадающий GnTrack можно получить с помощью albumResponse.trackMatched()

  • название трека можно получить в виде строки с помощью albumResponse.trackMatched().title().display()

  • исполнителя трека можно получить с помощью AlbumResponse.artist().name().display()

  • текущая позиция трека во времени может быть извлечена с помощью AlbumResponse.trackMatched().currentPosition(), который кажется довольно точным в определении времени окончания песни как {endTime = currentTime + duration - currentPosition}

  • продолжительность трека может быть извлечена с помощью albumResponse.trackMatched().duration()

  • URL-адрес обложки можно извлечь с помощью AlbumResponse.coverArt().asset(GnImageSize.kImageSizeSmall).urlHttp().

Мне не удалось получить изображение в виде строки base64 с помощью AlbumResponse.coverArt().asset(GnImageSize.kImageSizeSmall).imageDataBase64(), но GNSDK предоставил простой класс GnAssetFetch, который можно использовать для извлечения обложки. данные следующим образом

GnAssetFetch assetData = new GnAssetFetch(mGnUser,coverArtUrl);
byte[] data = assetData.data();

Что касается отмены выполняемой операции, то можно использовать метод identityCancel() экземпляра GnMusicIdStream. Если отмена будет происходить в методах обратного вызова IGnMusicIdStreamEvents, вместо этого следует использовать предоставленный отменитель IGnCancellable.

person CCJ    schedule 08.12.2017