从 Gracenote Mobile Client 迁移到 GNSDK for Mobile 时遇到问题

Trouble migrating from Gracenote Mobile Client to GNSDK for Mobile

我正在努力将我的 Android 应用程序从旧版 Gracenote 移动客户端迁移到更新的 GNSDK for Mobile SDK,但遇到了一些问题:

  1. 在移动端,我使用了GNOperations.recognizeMIDStreamFromRadio(GNSearchResultReady, GNConfig, samplePCMBuffer) 在 PCM 缓冲区上启动指纹和查找操作。我的应用程序只能向 Gracenote 提供预先录制的音频(而不是简单地将 Gracenote 指向流式音频源),理想情况下作为原始 PCM,但我可以在必要时编码为标准压缩格式。我应该使用 GNSDK for Mobile API 中的什么来对提供的预录制音频数据进行相同的指纹和查找操作,希望这些数据仍然是原始 PCM?
  2. class GnMusicId 看起来可能是一个方便的通用指纹生成器和查询发布器 class,所以它可能是上面 #1 的答案。但是,我还没有找到确定它何时完成指纹写入的方法,因此,我们准备发出查询。如何获得回调,让我知道 GnMusicId 已完成从 GnMusicId.fingerprintWrite(byte[] audioData, long audioDataSize) 方法写入指纹,并且指纹已准备好通过 [=22 在查询中使用=](fingerprintDataGet(), GnFingerprintType.kFingerprintTypeStream6)?
  3. 在 Mobile Client 中,我能够使用 GNOperations.cancel(GNSearchResultReady) 取消正在进行的 Gracenote 操作——我读到新架构要求由于更加模块化的设计而单独取消特定操作,但是GNSDK for Mobile 可以执行的各种操作,我还没有找到标准取消 API -- 我应该如何取消 GNSDK for Mobile 中的指纹和歌曲查找操作?

事实证明,您可以通过以下三个 GnMusicIdStream API 调用识别 GNSDK 中给定的 Android PCM 数组:

  1. GnMusicIdStream.audioProcessStart(sampleRateHz,pcmBitcount,channelCount)为传入的PCM准备识别引擎
  2. GnMusicIdStream.audioProcess(pcmArray,pcmArray.length)传入要识别的PCM数组
  3. GnMusicIdStream.identifyAlbumAsync() 生成指纹,然后将其用于查找操作。这将导致对传递给您的 GnMusicIdStream 实例的 IGnMusicIdStreamEvents object 的回调,并且 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);
    }

return 数据有点令人困惑,有多种可能的方法来提取有关轨道的元数据,当以某些方式查询时可能为 null 或空,而以其他方式查询时则不会。到目前为止,我发现关于 GnResponseAlbums 响应 object 的有趣点(我不确定下面提到的 return 值的可空性契约,因此请注意 nullpointerexceptions):

  • gnResponseAlbums.resultCount() 如果没有明显错误但未找到匹配项,则为 0。

  • 可以用albumResponse.trackMatched()

  • 检索匹配的GnTrack
  • 曲目标题可以通过 albumResponse.trackMatched().title().display()

  • 检索为字符串
  • 可以使用 albumResponse.artist().name().display()

  • 检索曲目艺术家
  • 可以使用 albumResponse.trackMatched().currentPosition() 提取当前曲目的时间位置,这在确定歌曲结束时间方面似乎相当准确 {endTime =当前时间 + 持续时间 - 当前位置}

  • 可以使用 albumResponse.trackMatched().duration()

  • 提取曲目的持续时间
  • 封面URL可以用albumResponse.coverArt().asset(GnImageSize.kImageSizeSmall).urlHttp()提取。

我没有运气通过 albumResponse.coverArt().asset(GnImageSize.kImageSizeSmall).imageDataBase64() 将图像捆绑为 base64 字符串,但是 GNSDK 提供了一个简单的 GnAssetFetch class 可用于下拉封面数据如下

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

至于取消正在进行的操作,可以使用 GnMusicIdStream 实例的 identifyCancel() 方法。如果取消将在 IGnMusicIdStreamEvents 回调方法中发生,则应改用提供的 IGnCancellable 取消器。