Android 录制通话参数

Android Recording Call parameters

我正在开发 DTMF 解码器。我需要的是录制语音通话,然后提取频率范围。一切正常,但有一些 android 版本在设置音频源时出现以下错误

"Invalid capture preset 3 for AudioAttributes"

为了得到正确的参数我开发了一个算法:

private static final int[] FREQUENCY = {8000, 11025, 16000, 22050, 44100}; // 44100 is guaranteed to work in all devices

private static final int[] CHANNEL_CONFIGURATION = {AudioFormat.CHANNEL_IN_MONO,
                                                    AudioFormat.CHANNEL_IN_STEREO};

private static final int[] AUDIO_ENCODING = {AudioFormat.ENCODING_DEFAULT,
                                             AudioFormat.ENCODING_PCM_8BIT,
                                             AudioFormat.ENCODING_PCM_16BIT};

for (int i = 0; i < FREQUENCY.length && !found; i ++) {
        for (int j = 0; j < CHANNEL_CONFIGURATION.length && !found; j ++) {
            for (int k = 0; k < AUDIO_ENCODING.length && !found; k ++) {
                try {
                    bufferSize = AudioRecord.getMinBufferSize(FREQUENCY[i], CHANNEL_CONFIGURATION[j], AUDIO_ENCODING[k]);
                    if (bufferSize != AudioRecord.ERROR_BAD_VALUE && bufferSize != AudioRecord.ERROR) {
                        audioRecord = new AudioRecord(MediaRecorder.AudioSource.VOICE_DOWNLINK, FREQUENCY[i], CHANNEL_CONFIGURATION[j], AUDIO_ENCODING[k], bufferSize);
                        found = true;
                    }
                } catch (Exception e) {
                    Log.e(TAG, e.toString());
                }
            }
        }
    }

找不到 api 19 或 22 的正确参数来设置 AudioRecord。在每种情况下都会引发异常。 我对此很困惑。我没有考虑使用 MediaRecoder class,因为我无法直接从编码器读取缓冲区,这对于 dtmf 解码过程至关重要。我也看过一些dtmf开源解码器,但都存在这个问题

结论

Android官方BUG

第一

AudioRecord.java 它的构造函数 public AudioRecord(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes) 可能不推荐使用(我认为),一个 IllegalArgumentException 被直接抛出,另一个 构造方法如下(尤其是 CANDIDATE FOR PUBLIC API):

 /**
 * @hide
 * CANDIDATE FOR PUBLIC API
 * Class constructor with {@link AudioAttributes} and {@link AudioFormat}.
 * @param attributes a non-null {@link AudioAttributes} instance. Use
 *     {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the capture
 *     preset for this instance.
 * @param format a non-null {@link AudioFormat} instance describing the format of the data
 *     that will be recorded through this AudioRecord. See {@link AudioFormat.Builder} for
 *     configuring the audio format parameters such as encoding, channel mask and sample rate.
 * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written
 *   to during the recording. New audio data can be read from this buffer in smaller chunks
 *   than this size. See {@link #getMinBufferSize(int, int, int)} to determine the minimum
 *   required buffer size for the successful creation of an AudioRecord instance. Using values
 *   smaller than getMinBufferSize() will result in an initialization failure.
 * @param sessionId ID of audio session the AudioRecord must be attached to, or
 *   {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at construction
 *   time. See also {@link AudioManager#generateAudioSessionId()} to obtain a session ID before
 *   construction.
 * @throws IllegalArgumentException
 */
public AudioRecord(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,int sessionId) throws IllegalArgumentException {
}

你可以试试

/** Voice call uplink + downlink audio source */ public static final int VOICE_CALL = 4;

第三

 /**
     * @hide
     * Sets the capture preset.
     * Use this audio attributes configuration method when building an {@link AudioRecord}
     * instance with {@link AudioRecord#AudioRecord(AudioAttributes, AudioFormat, int)}.
     * @param preset one of {@link MediaRecorder.AudioSource#DEFAULT},
     *     {@link MediaRecorder.AudioSource#MIC}, {@link MediaRecorder.AudioSource#CAMCORDER},
     *     {@link MediaRecorder.AudioSource#VOICE_RECOGNITION} or
     *     {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
     * @return the same Builder instance.
     */
    @SystemApi
    public Builder setCapturePreset(int preset) {
        //....
        Log.e(TAG, "Invalid capture preset " + preset + " for AudioAttributes");
    }

参考资源

AudioRecord.java

AudioAttributes.java

AudioAttributes.java

@SystemApi @hide

https://code.google.com/p/android/issues/detail?id=2117&q=call%20recorder&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars

https://code.google.com/p/android/issues/detail?id=4075

某些 android 版本禁用了此功能。如果你有 android 源代码,你可以让它工作。我目前正在使用 cyanogenmod,所以我自定义了 AudioAttributes.java class 以便在发生异常时不引发异常。

我们只需更改 AudioAttributes.java 中的 setCapturePreset() 方法,方法是在 switch/case 结构中添加我们想要的所有音频源。

这是原文:

/**
     * @hide
     * Sets the capture preset.
     * Use this audio attributes configuration method when building an {@link AudioRecord}
     * instance with {@link AudioRecord#AudioRecord(AudioAttributes, AudioFormat, int)}.
     * @param preset one of {@link MediaRecorder.AudioSource#DEFAULT},
     *     {@link MediaRecorder.AudioSource#MIC}, {@link MediaRecorder.AudioSource#CAMCORDER},
     *     {@link MediaRecorder.AudioSource#VOICE_RECOGNITION} or
     *     {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
     * @return the same Builder instance.
     */
    @SystemApi
    public Builder setCapturePreset(int preset) {
        switch (preset) {
            case MediaRecorder.AudioSource.DEFAULT:
            case MediaRecorder.AudioSource.MIC:
            case MediaRecorder.AudioSource.CAMCORDER:
            case MediaRecorder.AudioSource.VOICE_RECOGNITION:
            case MediaRecorder.AudioSource.VOICE_COMMUNICATION:
                mSource = preset;
                break;
            default:
                Log.e(TAG, "Invalid capture preset " + preset + " for AudioAttributes");
        }
        return this;
    }

我用这个代替:

/**
     * @hide
     * Sets the capture preset.
     * Use this audio attributes configuration method when building an {@link AudioRecord}
     * instance with {@link AudioRecord#AudioRecord(AudioAttributes, AudioFormat, int)}.
     * @param preset one of {@link MediaRecorder.AudioSource#DEFAULT},
     *     {@link MediaRecorder.AudioSource#MIC}, {@link MediaRecorder.AudioSource#CAMCORDER},
     *     {@link MediaRecorder.AudioSource#VOICE_RECOGNITION} or
     *     {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
     * @return the same Builder instance.
     */
    @SystemApi
    public Builder setCapturePreset(int preset) {
        switch (preset) {
            case MediaRecorder.AudioSource.DEFAULT:
            case MediaRecorder.AudioSource.MIC:
            case MediaRecorder.AudioSource.CAMCORDER:
            case MediaRecorder.AudioSource.VOICE_RECOGNITION:
            case MediaRecorder.AudioSource.VOICE_COMMUNICATION:
            case MediaRecorder.AudioSource.VOICE_DOWNLINK:
            case MediaRecorder.AudioSource.VOICE_UPLINK:
            case MediaRecorder.AudioSource.VOICE_CALL:
                mSource = preset;
                break;
            default:
                Log.e(TAG, "Invalid capture preset " + preset + " for AudioAttributes");
        }
        return this;
    }