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