从语音识别意图中记录/保存音频

32

我想保存/记录Google识别服务用于语音转文本操作的音频(使用RecognizerIntent或SpeechRecognizer)。

我尝试了许多方法:

  1. 从RecognitionListener的onBufferReceived:我知道,这不起作用,只是为了测试并且onBufferReceived从未被调用过(在带有JB 4.3的Galaxy Nexus上进行了测试)

  2. 使用媒体录制器:不起作用。它会破坏语音识别。麦克风仅允许一次操作

  3. 试图找到识别服务在执行语音转文本API之前保存临时音频文件的位置以复制它,但没有成功

我几乎绝望了,但我发现Google Keep应用程序正在做我需要做的事情!我使用logcat调试了一下保留应用程序,并且该应用程序也调用了“ RecognizerIntent.ACTION_RECOGNIZE_SPEECH”(就像我们开发人员所做的那样)来触发语音转文本。但是,Keep是如何保存音频的呢?它可能是隐藏的API吗?Google在“作弊”吗?

4个回答

31

@Kaarel的答案几乎非常完整 - 生成的音频在intent.getData()中,可以使用ContentResolver读取。

不幸的是,返回的AMR文件质量较低 - 我无法找到获取高质量录音的方法。除“audio/AMR”之外的任何值在intent.getData()中返回null。

如果您找到获取高质量录音的方法,请发表评论或添加答案!

public void startSpeechRecognition() {
   // Fire an intent to start the speech recognition activity.
   Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
   // secret parameters that when added provide audio url in the result
   intent.putExtra("android.speech.extra.GET_AUDIO_FORMAT", "audio/AMR");
   intent.putExtra("android.speech.extra.GET_AUDIO", true);

   startActivityForResult(intent, "<some code you choose>");
}

// handle result of speech recognition
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // the resulting text is in the getExtras:
    Bundle bundle = data.getExtras();
    ArrayList<String> matches = bundle.getStringArrayList(RecognizerIntent.EXTRA_RESULTS)
    // the recording url is in getData:
    Uri audioUri = data.getData();
    ContentResolver contentResolver = getContentResolver();
    InputStream filestream = contentResolver.openInputStream(audioUri);
    // TODO: read audio file from inputstream
}

2
这可能是一个非常长的尝试,但是...我让它工作了。然而,它会打开一个对话框来说话,我通过实现RecognitionListener绕过了这个问题,但是由于我重写的public void onResults(Bundle results)不包含Intent,我找不到任何方法来获取Intent,因此无法检索URI。 - Fredrik
@fredrik,这对我来说也是一个主要问题。根据文档,使用onBufferReceived(byte[] buffer)似乎不是一种合适的方法。你能找到解决办法吗? - nonybrighto
1
我尝试了这个方法,但现在它已经不起作用了。当我添加那些秘密参数时,它甚至不会显示语音识别对话框。也许这个黑客方法只适用于旧的SDK版本。你有什么想法吗? - Rahul Bansal
4
进一步的翻译如下:InputStream filestream = contentResolver.openInputStream(audioUri); byte[] buffer = new byte[filestream.available()]; filestream.read(buffer); OutputStream outStream = new FileOutputStream(audiofile); outStream.write(buffer);请确保你已经有了一个名为audiofile的文件描述符。 - aac
2
@Haider Saleem 我使用 RecognizerIntent 来识别用户的语音,至少我可以通过 MediaPlayer 回放他/她的语音。 - Andrey Epifantsev
显示剩余7条评论

10
上次我检查时,Google Keep设置了以下额外内容:
  • android.speech.extra.GET_AUDIO_FORMAT:audio/AMR
  • android.speech.extra.GET_AUDIO:true
这些内容并未在Android文档中记录,因此它们不构成Android API的一部分。此外,Google Keep不依赖于识别器意图来考虑这些额外内容。如果谷歌能够推广和记录这些额外内容,那将是很好的。
为了找出Google Keep在调用RecognizerIntent时设置了哪些额外内容,请实现一个应用程序来响应RecognizerIntent并打印出它所接收到的所有额外内容。您还可以安装Kõnele (http://kaljurand.github.io/K6nele/),这是RecognizerIntent的实现。当Google Keep启动Kõnele时,长按扳手形状的设置图标。这会显示一些有关调用者的技术细节,并包括传入的额外内容。
@Iftah的答案解释了Google Keep如何将音频记录返回给RecognizerIntent的调用者。

1
你是怎么发现“keep”设置了这些额外内容的? - Slim
感谢你的答案。我已经按照你的建议实施了,你是对的,Google Keep只是使用了所提到的额外参数来启动RecognizerIntent。我尝试使用与Google Keep相同的额外参数来启动RecognizerIntent,但结果得到的Intent没有包含任何额外参数!!!Google Keep是如何做到的,我们可以在Android官方问题跟踪器上询问吗?如果有任何Google员工看到这个,请帮助我们好吗?谢谢。 - Slim
@Slim 你确定没有额外的附加组件吗?你仔细检查了所有的捆绑包吗?还有捆绑在捆绑包里面的吗? - Kaarel
2
@Slim @Kaarel 结果在 intent.getData() 中而不是 getExtras() 中。结果是一个内容 URL,您需要使用 ContentResolver 打开它。 - Iftah
有人知道如何保存除AMR编码音频以外的任何16KHz X 16bits格式吗? - Tal Weiss
显示剩余2条评论

4
我从这里得到了答案,我检查了日期,发现它是在你的帖子几天后发布的,所以我想你可能错过了它。 Android同时识别语音和录音 那里有个人说:
我找到了一个解决方案,可以很好地进行语音识别和音频录制。 这里(https://github.com/katchsvartanian/voiceRecognition)是我创建的一个简单的Android项目,用于展示该解决方案的工作原理。 此外,我在项目中放置了一些屏幕截图来说明应用程序。
我将尝试简要解释我使用的方法。 我在该项目中结合了两个功能:Google语音API和Flac录音。
通过HTTP连接调用Google语音API。 Mike Pultz提供了有关API的更多详细信息:
“(...)新的[Google] API是一个全双工流媒体API。 这意味着它实际上使用两个HTTP连接-一个POST请求将内容作为“实时”分块流上传,并且第二个GET请求访问结果,这对于更长的音频样本或流式音频更有意义。”
但是,此API需要接收FLAC音频文件才能正常工作。 这使我们进入第二部分:Flac录音
我通过从名为AudioBoo的开源应用程序中提取和适应一些代码和库来实现该项目的Flac录制。 AudioBoo使用本机代码记录和播放flac格式。
因此,可以记录FLAC声音,将其发送到Google语音API以获取文本,并播放刚刚录制的声音。
我创建的项目具有使其工作的基本原则,并且可以针对特定情况进行改进。 为了使它在不同的场景中工作,需要获得Google Speech API密钥,该密钥是通过成为Google Chromium-dev组的一部分而获得的。 我在该项目中留下了一个密钥,只是为了显示它正在工作,但我最终会删除它。 如果有人需要更多信息,请告诉我,因为我无法在此帖子中放置超过2个链接。

3
这并没有回答问题(即如何通过Android语音识别API进行记录)。 - Kaarel

0
我们可以使用AudioRecord类来保存这个音频。我已经成功地完成了这个任务。
public class MainActivity extends AppCompatActivity {
TextView textView;
ImageView imageView;
static int request = 1;
private static final int RECORDER_SAMPLERATE = 8000;
private static final int RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO;
private static final int RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
private AudioRecord recorder = null;
private Thread recordingThread = null;
private boolean isRecording = false;
private int[] mSampleRates = new int[]{8000, 11025, 22050, 44100};
int bufferSize;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    textView = findViewById(R.id.textView);
    imageView = findViewById(R.id.mic);


    int bufferSize = AudioRecord.getMinBufferSize(RECORDER_SAMPLERATE,
            RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING);


    recorder = findAudioRecord();

    if (ContextCompat.checkSelfPermission(this,
            Manifest.permission.RECORD_AUDIO)
            != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE},
                1234);
    }
    
    imageView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent speech = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
            speech.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
            speech.putExtra(RecognizerIntent.EXTRA_PROMPT, "Speak to Text");

            if (ContextCompat.checkSelfPermission(MainActivity.this,
                    Manifest.permission.RECORD_AUDIO)
                    == PackageManager.PERMISSION_GRANTED) {
                startRecording();
                startActivityForResult(speech, request);
            }

        }
    });

    textView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            stopRecording();
        }
    });
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == request && resultCode == RESULT_OK) {
        stopRecording();
        ArrayList<String> dataa = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
        textView.setText(dataa.get(0).toString());
    }
}

int BufferElements2Rec = 1024; // want to play 2048 (2K) since 2 bytes we use only 1024
int BytesPerElement = 2; // 2 bytes in 16bit format

private void startRecording() {

    recorder.startRecording();
    isRecording = true;
    recordingThread = new Thread(new Runnable() {
        public void run() {
            writeAudioDataToFile();
        }
    }, "AudioRecorder Thread");
    recordingThread.start();
}

@Override
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[], int[] grantResults) {
    switch (requestCode) {
        case 1234: {
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            } else {
                Log.d("TAG", "permission denied by user");
            }
            return;
        }
    }
}
private byte[] short2byte(short[] sData) {
    int shortArrsize = sData.length;
    byte[] bytes = new byte[shortArrsize * 2];
    for (int i = 0; i < shortArrsize; i++) {
        bytes[i * 2] = (byte) (sData[i] & 0x00FF);
        bytes[(i * 2) + 1] = (byte) (sData[i] >> 8);
        sData[i] = 0;
    }
    return bytes;

}
public AudioRecord findAudioRecord() {
    for (int rate : mSampleRates) {
        for (short audioFormat : new short[]{
                AudioFormat.ENCODING_PCM_8BIT,
                AudioFormat.ENCODING_PCM_16BIT}) {
            for (short channelConfig : new short[]{
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.CHANNEL_IN_STEREO}) {
                try {
                    Log.d("Mic2", "Attempting rate " + rate
                            + "Hz, bits: " + audioFormat
                            + ", channel: " + channelConfig);
                    bufferSize = AudioRecord.getMinBufferSize(rate,
                            channelConfig, audioFormat);

                        AudioRecord recorder = new AudioRecord(
                                MediaRecorder.AudioSource.DEFAULT, rate,
                                channelConfig, audioFormat, bufferSize);
                        if (recorder.getState() == AudioRecord.STATE_INITIALIZED)
                            rate = rate;
                        return recorder;
                } catch (Exception e) {
                    Log.e("TAG", rate + "Exception, keep trying.", e);
                }
            }
        }
    }
    return null;
}

private void writeAudioDataToFile() {
    String filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/file.pcm";
    short sData[] = new short[BufferElements2Rec];

    FileOutputStream os = null;
    try {
        os = new FileOutputStream(filePath);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }

    while (isRecording) {

        recorder.read(sData, 0, BufferElements2Rec);
        System.out.println("Short writing to file" + sData.toString());
        try {
            byte bData[] = short2byte(sData);
            os.write(bData, 0, BufferElements2Rec * BytesPerElement);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    try {
        os.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private void stopRecording() {
    if (null != recorder) {
        isRecording = false;
        recorder.stop();
        recorder.release();
        recorder = null;
        recordingThread = null;
    }
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        finish();
    }
    return super.onKeyDown(keyCode, event);
}

我尝试过这个,但是语音识别器在第一次听后停止识别,有时根本不听。我得到了mp3文件,但是语音识别器无法工作。 - confusedstudent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接