Use o AOQ para se conectar ao fun-asr-realtime, transmitir áudio do microfone e receber resultados de transcrição em tempo real. O código do cliente usa Android Java, e outras plataformas compatíveis com AOQ oferecem as mesmas interfaces.
Visão geral da solução
O fun-asr-realtime transcreve um fluxo de áudio em texto pontuado em tempo real. O SDK do AOQ transporta mídia e eventos por tracks separadas: o cliente envia áudio pela track de Audio e usa a track de Data para enviar eventos de controle e receber eventos de reconhecimento. O modelo utiliza o protocolo de evento Inference, não o protocolo de evento Realtime.
Esta solução é adequada para legendas ao vivo, transcrição de reuniões, entrada de voz e assistentes inteligentes. A track de Audio elimina a necessidade de codificar áudio em mensagens de evento, enquanto a track de Data preserva a semântica das tarefas, como run-task, result-generated e finish-task.
- O cliente solicita credenciais temporárias de conexão AOQ ao servidor de aplicativos.
- O servidor de aplicativos usa uma chave de API para solicitar um token ao Model Studio e retorna os campos de conexão ao cliente.
- O cliente estabelece uma conexão AOQ e envia run-task. Após receber task-started, o cliente começa a enviar o áudio do microfone.
- O servidor retorna continuamente eventos result-generated. Depois que o cliente envia finish-task, ele aguarda o resultado final e task-finished.
Pré-requisitos
- Ative o Alibaba Cloud Model Studio e crie uma chave de API conforme descrito em Obtain and configure an API key. Armazene a chave de API apenas no servidor de aplicativos. Não a inclua no código do cliente nem a confirme em um repositório de código.
- Selecione um endpoint AOQ para a região onde sua aplicação está implantada. Para seleção de endpoints, consulte Select a region, deployment scope, and endpoint.
- Baixe o AOQ Client SDK mais recente conforme descrito em SDK download. Este tutorial transporta áudio PCM e não requer o plugin opcional Opus.
- Configure um servidor de aplicativos conforme descrito em Token authentication e implemente a autenticação proxy no lado do servidor para o protocolo AOQ Inference. Antes de cada nova conexão, o cliente deve obter novas credenciais de conexão do servidor de aplicativos.
Importar o SDK
Escolha as instruções de integração do SDK para sua plataforma de desenvolvimento. A implementação do cliente usa Android Java; outras plataformas seguem o mesmo design de API e fluxo de eventos.
Android
- Coloque AoqClientSdk-release.aar em app/libs e configure a dependência e as ABIs em app/build.gradle:
android {
defaultConfig {
minSdk 21
ndk { abiFilters 'armeabi-v7a', 'arm64-v8a' }
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.aar'])
}
- Declare as permissões de rede e gravação no AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
- Solicite a permissão de tempo de execução RECORD_AUDIO antes do início da gravação. O reconhecimento de fala não requer a permissão CAMERA.
iOS
- Arraste AoqClientSdk.framework para o projeto Xcode. Em Target > General > Frameworks, Libraries, and Embedded Content, selecione Embed & Sign. O SDK suporta dispositivos arm64 que executam iOS 13.0 ou posterior.
- Adicione NSMicrophoneUsageDescription ao Info.plist. O reconhecimento de fala não requer NSCameraUsageDescription.
- Use import AoqClientSdk em Swift, ou #import <AoqClientSdk/AoqClientSdk.h> em Objective-C.
HarmonyOS
- Coloque AoqClientSdk.har em entry/libs e declare a dependência em entry/oh-package.json5. O SDK é compatível com a API 12 e suporta arm64-v8a:
{
"dependencies": {
"@aoq/client-sdk": "file:./libs/AoqClientSdk.har"
}
}
- Declare as permissões de rede e microfone em entry/src/main/module.json5:
"requestPermissions": [
{ "name": "ohos.permission.INTERNET" },
{
"name": "ohos.permission.MICROPHONE",
"reason": "$string:perm_mic_reason",
"usedScene": { "abilities": ["EntryAbility"], "when": "inuse" }
}
]
- Antes de iniciar a gravação, chame abilityAccessCtrl.createAtManager().requestPermissionsFromUser para solicitar acesso ao microfone.
Linux (Python)
- Extraia o SDK e mantenha aoq_client_sdk.py, libAoqClientSdk.so e libonnxruntime.so.1.16.3 no mesmo diretório.
- Adicione o diretório do SDK aos caminhos de busca de módulos Python e bibliotecas compartilhadas:
export PYTHONPATH="$PWD/AoqClientSdk:$PYTHONPATH"
export LD_LIBRARY_PATH="$PWD/AoqClientSdk:$LD_LIBRARY_PATH"
- Use import aoq_client_sdk em Python. Você também pode definir AOQ_CLIENT_SDK_LIB como o caminho absoluto de libAoqClientSdk.so.
Experimentar a demonstração
Use a demonstração Android do Alibaba Cloud Model Studio para verificar rapidamente a conectividade AOQ. Baixe o APK e configure a chave de API e workspaceId para testar modelos selecionados.
Escaneie o QR code a seguir para baixar a demonstração:
Fluxo de implementação
- O servidor de aplicativos usa a URL de token Inference para obter parâmetros de conexão AOQ para o fun-asr-realtime.
- O cliente mapeia a resposta do token para AoqConnectConfig, publica as tracks de Audio e Data e assina a track de Data.
- O cliente configura os parâmetros de codificação de áudio com base nos requisitos do negócio e do modelo, inicia a captura do microfone com o envio de áudio desativado e estabelece a conexão AOQ.
- Após o estabelecimento da conexão, o cliente envia run-task e ativa o envio pela track de Audio depois de receber task-started.
- O cliente processa result-generated em onDataMsg. Para interromper o reconhecimento, ele primeiro desativa o envio pela track de Audio e então envia finish-task.
- Depois de receber task-finished, o cliente pode iniciar outra tarefa na mesma conexão com um novo task_id, ou desconectar e destruir o engine.
Obter um token no servidor de aplicativos
Defina DASHSCOPE_API_KEY no servidor de aplicativos e envie uma solicitação para o endpoint da região selecionada. clientIp é o endereço IP público real do dispositivo cliente. O campo é opcional, mas recomendamos fornecê-lo para que o service possa selecionar um endpoint de retransmissão apropriado.
curl -X POST \
"https://{endpoint}/api/v1/webrtc/inference?model=fun-asr-realtime" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${DASHSCOPE_API_KEY}" \
-H "x-dashscope-rtc-transport: moq" \
-d "{\"clientIp\": \"${CLIENT_REAL_IP}\"}"
ObservaçãoSe o servidor de aplicativos não conseguir obter o endereço IP público real do dispositivo cliente, omita clientIp do corpo da solicitação em vez de passar uma string vazia.
Retorne os seguintes campos de resposta ao cliente. Nunca retorne a chave de API ao cliente. Para todos os campos de solicitação e resposta, consulte Token authentication.
Campo de resposta | Campo do SDK |
aoqTokenForClient | AoqConnectConfig.token |
sid | AoqConnectConfig.sid |
clientRelayCertFingerprint | AoqConnectConfig.certFingerprint |
clientRelayEndpoints | AoqConnectConfig.relayEndpoints |
extraInfo.workspaceIdHash | AoqConnectConfig.workspaceIdHash |
Implementar o cliente Android
Os passos a seguir dividem o código do cliente Android Java de acordo com a sequência real de conexão e tarefas. Cada trecho é retirado do exemplo completo apresentado posteriormente neste tópico.
1. Criar o engine e registrar callbacks
Crie um engine de cliente AOQ e registre callbacks para alterações de status de conexão e eventos da track de Data. Implemente a lógica de callback para sua aplicação e inicie o reconhecimento somente após o estabelecimento da conexão.
AoqClientListener listener = new AoqClientListener() {
@Override
public void onConnectionStatusChange(AoqClientEngine.AoqConnectionStatus status) {
connected = status == AoqClientEngine.AoqConnectionStatus
.AoqConnectionStatusConnected;
if (connected) {
beginRecognition();
}
}
@Override
public void onDataMsg(AoqClientEngine.AoqDataMsg msg) {
handleServerEvent(msg);
}
};
AoqClientEngine.AoqCreateConfig createConfig = new AoqClientEngine.AoqCreateConfig();
createConfig.workDir = context.getFilesDir().getAbsolutePath();
engine = AoqClientEngine.createEngine(context, createConfig, listener);
2. Configurar a codificação de áudio
Configure a codificação de áudio enviada ao modelo. Defina o formato, taxa de amostragem e contagem de canais com base nos requisitos da sua aplicação e do modelo. O código a seguir usa PCM mono de 16 kHz como exemplo. Para valores suportados, consulte os parâmetros de run-task em Client events.
AoqClientEngine.AoqAudioCodecConfig encoder =
new AoqClientEngine.AoqAudioCodecConfig();
encoder.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
encoder.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioPCM;
encoder.sampleRate = 16000;
encoder.channel = 1;
encoder.bitrate = 24000;
engine.setAudioEncoderConfig(encoder);
3. Configurar a conexão e as tracks de transporte
Configure a conexão AOQ com as credenciais retornadas pelo servidor de aplicativos e selecione as tracks para publicar e assinar com base na sua aplicação. Para reconhecimento de fala em tempo real, este exemplo publica as tracks de Audio e Data e assina a track de Data.
addTrack(connectConfig, true,
AoqClientEngine.AoqTrackType.AoqTrackTypeAudio);
addTrack(connectConfig, true,
AoqClientEngine.AoqTrackType.AoqTrackTypeData);
addTrack(connectConfig, false,
AoqClientEngine.AoqTrackType.AoqTrackTypeData);
4. Iniciar a captura de áudio e estabelecer a conexão
Configure a captura de áudio e estabeleça a conexão AOQ. Escolha captura integrada ou externa, se deve ativar o modo VoIP e a contagem de canais com base na sua aplicação. Mantenha o envio pela track de Audio desativado até receber task-started.
AoqClientEngine.AoqAudioCaptureConfig capture =
new AoqClientEngine.AoqAudioCaptureConfig();
capture.isExternal = false;
capture.isVoipMode = true;
capture.channel = 1;
engine.startAudioCapture(capture);
// Do not send audio until task-started is received.
engine.enableSendMediaStream(
AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, false);
engine.connect(connectConfig);
5. Iniciar uma tarefa de reconhecimento
Após estabelecer a conexão, gere um ID de tarefa e envie run-task para iniciar o reconhecimento. Configure model, format, sample_rate e outros parâmetros de tarefa para o modelo e entrada de áudio que você utiliza. Para detalhes, consulte Client events.
taskId = UUID.randomUUID().toString();
JSONObject header = createHeader("run-task");
JSONObject parameters = new JSONObject()
.put("format", "pcm")
.put("sample_rate", 16000);
JSONObject payload = new JSONObject()
.put("task_group", "audio")
.put("task", "asr")
.put("function", "recognition")
.put("model", "fun-asr-realtime")
.put("parameters", parameters)
.put("input", new JSONObject());
send(new JSONObject().put("header", header).put("payload", payload));
6. Processar eventos do servidor
Gerencie estados de tarefas, resultados de reconhecimento e erros, e passe os resultados para sua aplicação. Implemente callbacks com base nos requisitos de UI e gerenciamento de estado. Envie áudio apenas após receber task-started e exclua eventos de heartbeat das transcrições exibidas. Para o esquema completo de resposta, consulte Server events.
String eventName = header.optString("event", "");
if ("task-started".equals(eventName)) {
taskStarted = true;
engine.enableSendMediaStream(
AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, true);
} else if ("result-generated".equals(eventName)) {
JSONObject payload = event.optJSONObject("payload");
JSONObject output = payload == null ? null : payload.optJSONObject("output");
JSONObject sentence = output == null ? null : output.optJSONObject("sentence");
if (sentence != null && !sentence.optBoolean("heartbeat", false)) {
String text = sentence.optString("text", "");
if (!text.isEmpty()) {
resultListener.onResult(
text, sentence.optBoolean("sentence_end", false));
}
}
} else if ("task-finished".equals(eventName)) {
resetTaskState();
resultListener.onTaskFinished();
} else if ("task-failed".equals(eventName)) {
String message = header.optString("error_message", "Recognition failed");
resetTaskState();
resultListener.onError(message);
}
7. Concluir a tarefa de reconhecimento
Quando o usuário terminar a gravação atual, pare de enviar áudio e envie finish-task. Mantenha a conexão aberta até que a transcrição final e task-finished cheguem. Em seguida, você pode iniciar outra tarefa ou liberar a conexão com base no fluxo da sua aplicação. Para o formato do evento, consulte Client events.
engine.enableSendMediaStream(
AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, false);
JSONObject payload = new JSONObject().put("input", new JSONObject());
send(new JSONObject()
.put("header", createHeader("finish-task"))
.put("payload", payload));
8. Desconectar e destruir o engine
Libere a captura de áudio, a conexão AOQ e os recursos do engine quando a página for destruída ou o reconhecimento não for mais necessário. Escolha o ponto de liberação com base no ciclo de vida da sua aplicação e não libere recursos imediatamente após o envio de finish-task.
engine.enableSendMediaStream(
AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, false);
engine.stopAudioCapture();
engine.disconnect();
AoqClientEngine.destroy();
Exemplo completo
Esta classe Android Java mapeia a resposta JSON do servidor de aplicativos para AoqConnectConfig e combina a lógica anterior de conexão, captura, tarefa e liberação de recursos.
import android.content.Context;
import com.alibaba.aoq.clientsdk.AoqClientEngine;
import com.alibaba.aoq.clientsdk.AoqClientListener;
import org.json.JSONArray;
import org.json.JSONObject;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public final class AsrClient {
public interface ResultListener {
void onResult(String text, boolean sentenceEnd);
void onTaskFinished();
void onError(String message);
}
private final AoqClientEngine engine;
private final ResultListener resultListener;
private String taskId;
private boolean connected;
private boolean taskStarted;
public AsrClient(Context context, AoqClientEngine.AoqConnectConfig connectConfig,
ResultListener resultListener) {
this.resultListener = resultListener;
AoqClientListener listener = new AoqClientListener() {
@Override
public void onConnectionStatusChange(AoqClientEngine.AoqConnectionStatus status) {
connected = status == AoqClientEngine.AoqConnectionStatus
.AoqConnectionStatusConnected;
if (connected) {
beginRecognition();
}
}
@Override
public void onDataMsg(AoqClientEngine.AoqDataMsg msg) {
handleServerEvent(msg);
}
};
AoqClientEngine.AoqCreateConfig createConfig = new AoqClientEngine.AoqCreateConfig();
createConfig.workDir = context.getFilesDir().getAbsolutePath();
engine = AoqClientEngine.createEngine(context, createConfig, listener);
configureAudioEncoder();
configureTracks(connectConfig);
startAudioCapture();
engine.connect(connectConfig);
}
private void configureAudioEncoder() {
AoqClientEngine.AoqAudioCodecConfig encoder = new AoqClientEngine.AoqAudioCodecConfig();
encoder.trackType = AoqClientEngine.AoqTrackType.AoqTrackTypeAudio;
encoder.codecType = AoqClientEngine.AoqEncoderType.AoqEncoderTypeAudioPCM;
encoder.sampleRate = 16000;
encoder.channel = 1;
encoder.bitrate = 24000;
engine.setAudioEncoderConfig(encoder);
}
private static void configureTracks(AoqClientEngine.AoqConnectConfig connectConfig) {
addTrack(connectConfig, true, AoqClientEngine.AoqTrackType.AoqTrackTypeAudio);
addTrack(connectConfig, true, AoqClientEngine.AoqTrackType.AoqTrackTypeData);
addTrack(connectConfig, false, AoqClientEngine.AoqTrackType.AoqTrackTypeData);
}
private void startAudioCapture() {
AoqClientEngine.AoqAudioCaptureConfig capture =
new AoqClientEngine.AoqAudioCaptureConfig();
capture.isExternal = false;
capture.isVoipMode = true;
capture.channel = 1;
engine.startAudioCapture(capture);
engine.enableSendMediaStream(AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, false);
}
/** Starts another recognition task on the existing AOQ connection. */
public void beginRecognition() {
if (!connected || taskStarted || taskId != null) {
return;
}
taskId = UUID.randomUUID().toString();
JSONObject header = createHeader("run-task");
JSONObject parameters = new JSONObject()
.put("format", "pcm")
.put("sample_rate", 16000);
JSONObject payload = new JSONObject()
.put("task_group", "audio")
.put("task", "asr")
.put("function", "recognition")
.put("model", "fun-asr-realtime")
.put("parameters", parameters)
.put("input", new JSONObject());
send(new JSONObject().put("header", header).put("payload", payload));
}
/** Stops the current task. Wait for task-finished before closing the connection. */
public void finishRecognition() {
if (taskId == null) {
return;
}
engine.enableSendMediaStream(AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, false);
JSONObject payload = new JSONObject().put("input", new JSONObject());
send(new JSONObject()
.put("header", createHeader("finish-task"))
.put("payload", payload));
}
public void close() {
engine.enableSendMediaStream(AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, false);
engine.stopAudioCapture();
engine.disconnect();
AoqClientEngine.destroy();
}
private void handleServerEvent(AoqClientEngine.AoqDataMsg msg) {
if (msg == null || msg.data == null) {
return;
}
JSONObject event = new JSONObject(new String(msg.data, StandardCharsets.UTF_8));
JSONObject header = event.optJSONObject("header");
if (header == null) {
return;
}
String eventName = header.optString("event", "");
if ("task-started".equals(eventName)) {
taskStarted = true;
engine.enableSendMediaStream(
AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, true);
} else if ("result-generated".equals(eventName)) {
handleRecognitionResult(event);
} else if ("task-finished".equals(eventName)) {
resetTaskState();
resultListener.onTaskFinished();
} else if ("task-failed".equals(eventName)) {
String message = header.optString("error_message", "Recognition failed");
resetTaskState();
resultListener.onError(message);
}
}
private void handleRecognitionResult(JSONObject event) {
JSONObject payload = event.optJSONObject("payload");
JSONObject output = payload == null ? null : payload.optJSONObject("output");
JSONObject sentence = output == null ? null : output.optJSONObject("sentence");
if (sentence == null || sentence.optBoolean("heartbeat", false)) {
return;
}
String text = sentence.optString("text", "");
if (!text.isEmpty()) {
resultListener.onResult(text, sentence.optBoolean("sentence_end", false));
}
}
private void resetTaskState() {
engine.enableSendMediaStream(AoqClientEngine.AoqTrackType.AoqTrackTypeAudio, false);
taskStarted = false;
taskId = null;
}
private JSONObject createHeader(String action) {
return new JSONObject()
.put("action", action)
.put("task_id", taskId)
.put("streaming", "duplex");
}
private void send(JSONObject event) {
AoqClientEngine.AoqDataMsg msg = new AoqClientEngine.AoqDataMsg();
msg.data = event.toString().getBytes(StandardCharsets.UTF_8);
engine.sendDataMsg(msg);
}
private static void addTrack(AoqClientEngine.AoqConnectConfig config, boolean publish,
AoqClientEngine.AoqTrackType type) {
AoqClientEngine.AoqTrackParam track = new AoqClientEngine.AoqTrackParam();
track.trackType = type;
if (publish) {
config.publishTracks.add(track);
} else {
config.subscribeTracks.add(track);
}
}
/** Maps the AppServer token response to an SDK connection configuration. */
public static AoqClientEngine.AoqConnectConfig parseConnectConfig(String responseText) {
JSONObject response = new JSONObject(responseText);
AoqClientEngine.AoqConnectConfig config = new AoqClientEngine.AoqConnectConfig();
config.token = response.optString("aoqTokenForClient", "");
config.sid = response.optString("sid", "");
config.certFingerprint = response.optString("clientRelayCertFingerprint", "");
JSONArray endpoints = response.optJSONArray("clientRelayEndpoints");
if (endpoints != null) {
for (int i = 0; i < endpoints.length(); i++) {
JSONObject item = endpoints.optJSONObject(i);
if (item == null) {
continue;
}
AoqClientEngine.AoqRelayEndpoint endpoint =
new AoqClientEngine.AoqRelayEndpoint();
endpoint.routeIndex = item.has("route_index")
? item.optInt("route_index", i) : i;
endpoint.endpoint = item.optString("endpoint", "");
endpoint.port = item.optInt("port", 0);
config.relayEndpoints.add(endpoint);
}
}
JSONObject extraInfo = response.optJSONObject("extraInfo");
config.workspaceIdHash = extraInfo == null
? "" : extraInfo.optString("workspaceIdHash", "");
return config;
}
}
Exemplo de uso
Passe a resposta do token do servidor de aplicativos para parseConnectConfig e crie o cliente. A primeira tarefa de reconhecimento inicia automaticamente após o estabelecimento da conexão. O botão de parar encerra apenas a tarefa atual. Libere a conexão e os recursos locais quando a página for destruída.
private AsrClient client;
void startRecognition(Context context, String tokenResponseText) {
AoqClientEngine.AoqConnectConfig config =
AsrClient.parseConnectConfig(tokenResponseText);
client = new AsrClient(context, config, new AsrClient.ResultListener() {
@Override
public void onResult(String text, boolean sentenceEnd) {
// Update the UI with the partial or final sentence.
}
@Override
public void onTaskFinished() {
// Enable the start button or call beginRecognition() for another task.
}
@Override
public void onError(String message) {
// Display or log the error.
}
});
}
void onStopButtonClick() {
// Finish the task and keep the AOQ connection open until task-finished arrives.
client.finishRecognition();
}
void onPageDestroyed() {
// Release local resources only when the page is closed.
client.close();
}
Executar e verificar
- Inicie o servidor de aplicativos. Certifique-se de que a solicitação de token retorne HTTP 200 e inclua sid, aoqTokenForClient, clientRelayEndpoints, clientRelayCertFingerprint e extraInfo.workspaceIdHash.
- Instale e execute o aplicativo em um dispositivo Android, conceda acesso ao microfone e fale.
- Observe os callbacks. Uma sequência normal de eventos é:
task-started
result-generated (sentence_end=false)
result-generated (sentence_end=true)
task-finished
Transcrições parciais são retornadas enquanto você fala. Após chamar finishRecognition, a transcrição final da frase atual e task-finished são retornados. Não desconecte imediatamente após o envio de finish-task.
Cenários comuns
Várias tarefas de reconhecimento em uma única conexão
Após receber task-finished, chame beginRecognition para iniciar outra tarefa na mesma conexão AOQ. Cada tarefa requer um novo task_id. Não é necessário solicitar outro token ou reconectar enquanto a conexão permanecer ativa. Se a conexão for fechada, obtenha novas credenciais de conexão.
Reconhecimento em segundo plano no Android
No Android 10 ou posterior, use um serviço em primeiro plano com foregroundServiceType=microphone para continuar a captura do microfone enquanto o aplicativo estiver em segundo plano. Inicie o serviço enquanto o aplicativo ainda estiver visível para o usuário.
Solução de problemas
Problema | Solução |
Falha na conexão | Verifique se o token é válido, se o endpoint corresponde à região da aplicação e se o servidor de aplicativos passa o endereço IP público real do dispositivo cliente. Não reutilize um token após o fechamento da conexão. |
A tarefa inicia, mas nenhuma transcrição é retornada | Ative o envio pela track de Audio apenas após receber task-started. Verifique o formato de entrada, taxa de amostragem e outros parâmetros em relação a Client events para o modelo. |
A transcrição final não é retornada | Desative o envio pela track de Audio antes de enviar finish-task. Aguarde o evento final result-generated e task-finished em vez de desconectar imediatamente. |
Falha ao carregar o SDK Android | Certifique-se de que o AAR esteja incluído como dependência e que o aplicativo empacote uma ABI suportada pelo SDK: armeabi-v7a ou arm64-v8a. |
Uma tarefa subsequente na mesma conexão é rejeitada | Confirme que task-finished foi recebido para a tarefa anterior e gere um novo task_id para o próximo evento run-task. |
Tópicos relacionados
Para todos os parâmetros, campos de eventos e APIs para outras plataformas, consulte: