4

我用 audoiRecord 类录制音频。现在我想将音频原始文件转换为 *flac 格式。我接下来将 *raw 文件转换为 wav:

private void copyWaveFile(String inFilename,String outFilename){
    FileInputStream in = null;
    FileOutputStream out = null;
    long totalAudioLen = 0;
    long totalDataLen = totalAudioLen + 36;
    long longSampleRate = sampleRate;
    int channels = 2;
    long byteRate = RECORDER_BPP * sampleRate * channels/8;
    byte[] data_pcm = new byte[mAudioBufferSize];
    try {
        in = new FileInputStream(inFilename);
        out = new FileOutputStream(outFilename);
        totalAudioLen = in.getChannel().size();
        totalDataLen = totalAudioLen + 36;
        Log.i(TAG,"File size: " + totalDataLen);
        WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
        longSampleRate, channels, byteRate);
        while(in.read(data_pcm) != -1){
            out.write(data_pcm);
        }
        in.close();
        out.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

这段代码负责文件头

private void WriteWaveFileHeader(
                 FileOutputStream out, long totalAudioLen,
                 long totalDataLen, long longSampleRate, int channels,
                 long byteRate) throws IOException {

     byte[] header = new byte[44];

     header[0] = 'R';  // RIFF/WAVE header
     header[1] = 'I';
     header[2] = 'F';
     header[3] = 'F';
     header[4] = (byte) (totalDataLen & 0xff);
     header[5] = (byte) ((totalDataLen >> 8) & 0xff);
     header[6] = (byte) ((totalDataLen >> 16) & 0xff);
     header[7] = (byte) ((totalDataLen >> 24) & 0xff);
     header[8] = 'W';
     header[9] = 'A';
     header[10] = 'V';
     header[11] = 'E';
     header[12] = 'f';  // 'fmt ' chunk
     header[13] = 'm';
     header[14] = 't';
     header[15] = ' ';
     header[16] = 16;  // 4 bytes: size of 'fmt ' chunk
     header[17] = 0;
     header[18] = 0;
     header[19] = 0;
     header[20] = 1;  // format = 1
     header[21] = 0;
     header[22] = (byte) channels;
     header[23] = 0;
     header[24] = (byte) (longSampleRate & 0xff);
     header[25] = (byte) ((longSampleRate >> 8) & 0xff);
     header[26] = (byte) ((longSampleRate >> 16) & 0xff);
     header[27] = (byte) ((longSampleRate >> 24) & 0xff);
     header[28] = (byte) (byteRate & 0xff);
     header[29] = (byte) ((byteRate >> 8) & 0xff);
     header[30] = (byte) ((byteRate >> 16) & 0xff);
     header[31] = (byte) ((byteRate >> 24) & 0xff);
     header[32] = (byte) (2 * 16 / 8);  // block align
     header[33] = 0;
     header[34] = RECORDER_BPP;  // bits per sample
     header[35] = 0;
     header[36] = 'd';
     header[37] = 'a';
     header[38] = 't';
     header[39] = 'a';
     header[40] = (byte) (totalAudioLen & 0xff);
     header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
     header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
     header[43] = (byte) ((totalAudioLen >> 24) & 0xff);

     out.write(header, 0, 44);
}

我不明白 *flac 文件的参数应该是什么

4

4 回答 4

3

您需要一个编码器来将 pcm 数据转换为 flac 格式。您不能只更改标题并期望内容像 flac 一样工作。

Android(至少在 4.1 之前)不包括 FLAC 编码器,尽管从 3.1 开始支持解码器(来源:http: //developer.android.com/guide/appendix/media-formats.html)。

我没有直接经验,但见过人们使用 ffmpeg 作为 flac 编码器。这个包含原生 libFLAC/libFLAC++ 编码器的项目audioboo-android看起来很有趣。

于 2013-02-25T05:04:58.030 回答
1

因此,从 Android 4.1 开始,您可以这样做:

初始化:

MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);

MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "audio/flac");
format.setInteger(MediaFormat.KEY_BIT_RATE, 64000);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, NUM_CHANNELS);

String codecname = mcl.findEncoderForFormat(format);
Log.w(TAG, "Codec: "+codecname);

MediaCodec codec = null;
try
{
    codec = MediaCodec.createByCodecName(codecname);
} catch (IOException e)
{
    e.printStackTrace();
}

codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
MediaFormat outputFormat = codec.getOutputFormat(); // option B

long usec = 1000000000L * FRAME_SIZE/SAMPLE_RATE;
MediaCodec.BufferInfo bufinfo = new MediaCodec.BufferInfo();
bufinfo.set(0, FRAME_SIZE * NUM_CHANNELS * 2, usec, 0);

codec.start();

byte[] inBuf = new byte[FRAME_SIZE * NUM_CHANNELS * 2];
byte[] encBuf = new byte[10240];

在记录器循环中:

int encoded = 0;
int inputBufferId = codec.dequeueInputBuffer(1000);
if (inputBufferId >= 0) {
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
    // fill inputBuffer with valid data
    inputBuffer.put(inBuf, 0, inBuf.length);
    codec.queueInputBuffer(inputBufferId, 0, inBuf.length, usec, 0);
}

int outputBufferId = codec.dequeueOutputBuffer(bufinfo, 1000);
if (outputBufferId >= 0) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A
    // bufferFormat is identical to outputFormat
    // outputBuffer is ready to be processed or rendered.

    outputBuffer.rewind();
    encoded = outputBuffer.remaining();
    outputBuffer.get(encBuf, 0, encoded);

    codec.releaseOutputBuffer(outputBufferId, false);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    // Subsequent data will conform to new format.
    // Can ignore if using getOutputFormat(outputBufferId)
    outputFormat = codec.getOutputFormat(); // option B
}

if (encoded > 0)
{
    // Process data in encBuf
}
于 2020-04-11T21:05:12.270 回答
0

这是一个纯 java FLAC 编码器:http: //javaflacencoder.sourceforge.net

一些类使用 javax api,但可以安全地删除它们而不影响主编码器类。

这是一些示例代码。record对象是类型AudioRecord

try {
                    // Path to write files to
                    String path = Environment.getExternalStoragePublicDirectory("/test").getAbsolutePath();

                    String fileName = name+".flac";
                    String externalStorage = path;
                    File file = new File(externalStorage + File.separator + fileName);

                    // if file doesnt exists, then create it
                    if (!file.exists()) {
                        file.createNewFile();
                    }
                    short sData[] = new short[BufferElements2Rec];

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


                    FLACEncoder flacEncoder = new FLACEncoder();
                    StreamConfiguration streamConfiguration = new StreamConfiguration(1,StreamConfiguration.MIN_BLOCK_SIZE,StreamConfiguration.MAX_BLOCK_SIZE,44100,16);

                    FLACFileOutputStream flacOut = new FLACFileOutputStream(os);
                    flacEncoder.setStreamConfiguration(streamConfiguration);
                    flacEncoder.setOutputStream(flacOut);
                    flacEncoder.openFLACStream();


                    record.startRecording();

                    int totalSamples = 0;

                    while (isRecording) {
                        record.read(sData, 0, BufferElements2Rec);
                        totalSamples+=BufferElements2Rec;

                        flacEncoder.addSamples(short2int(sData),BufferElements2Rec);

                        flacEncoder.encodeSamples(BufferElements2Rec, false);
                    }

                    int available = flacEncoder.samplesAvailableToEncode();
                    while(flacEncoder.encodeSamples(available,true) < available) {
                        available = flacEncoder.samplesAvailableToEncode();
                    }
                    try {
                        flacOut.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    record.stop();
                } catch(IOException ex) {
                    ex.printStackTrace();

                }

                record.release();
                record = null;
}

用于将短数据转换为 int 数据:

private int[] short2int(short[] sData) {
    int length = sData.length;
    int[] iData = new int[length];
    for(int i=0;i<length;i++) {
        iData[i] = sData[i];
    }
    return iData;
}
于 2018-09-11T21:34:01.623 回答
0

基于https://github.com/nieldeokar/WhatsappAudioRecorder/blob/master/app/src/main/java/com/nieldeokar/whatsappaudiorecorder/recorder/AudioRecordThread.java

我在工作语音识别时将录音保存在 .m4a 文件中的解决方案:

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.Handler;
import android.os.Looper;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashSet;

import timber.log.Timber;

public class SpeechRecognizer {

private static final int CHANNELS = 1;
private static final int BIT_RATE = 32000;
private static final int SAMPLE_RATE = 44100;
private static final int SAMPLE_RATE_INDEX = 4;

protected static final String TAG = SpeechRecognizer.class.getSimpleName();

public int bufferSize;

public final Collection<RecognitionListener> listeners = new HashSet();

public final Handler mainHandler = new Handler(Looper.getMainLooper());

public final Recognizer recognizer;
private Thread recognizerThread;

public final AudioRecord recorder;

private SoundAmplitudeCallback soundAmplitudeCallback;

private File recordFile = null;

private boolean isRecordingToFileEnabled = false;
private boolean isRecordingToFilePrepared = false;
private boolean isContinueRecordingToFile = false;

public interface SoundAmplitudeCallback {
    void onAmplitude(int amplitude);
}

public void setSoundAmplitudeCallback(SoundAmplitudeCallback callback) {
    soundAmplitudeCallback = callback;
}

public SpeechRecognizer(Mabcd model) throws IOException {
    this.recognizer = new Recognizer(model, SAMPLE_RATE);
    this.bufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE,  AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
    this.recorder = createAudioRecorder(this.bufferSize);

    if (this.recorder.getState() == 0) {
        this.recorder.release();
        throw new IOException("Failed to initialize recorder. Microphone might  be already in use.");
    }
}

public void addListener(RecognitionListener listener) {
    synchronized (this.listeners) {
        this.listeners.add(listener);
    }
}

public void removeListener(RecognitionListener listener) {
    synchronized (this.listeners) {
        this.listeners.remove(listener);
    }
}

public boolean startListening() {
    if (this.recognizerThread != null) {
        return false;
    }
    this.recognizerThread = new RecognizerThread(this);
    this.recognizerThread.start();

    return true;
}

public boolean startListening(int timeout) {
    if (this.recognizerThread != null) {
        return false;
    }
    this.recognizerThread = new RecognizerThread(timeout);
    this.recognizerThread.start();
    return true;
}

private boolean stopRecognizerThread() {
    if (this.recognizerThread == null) {
        return false;
    }
    try {
        this.recognizerThread.interrupt();
        this.recognizerThread.join();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    this.recognizerThread = null;
    return true;
}

public void startRecordToFile(File fileRecord) {
    this.recordFile = fileRecord;
    isRecordingToFileEnabled = true;
}

public void resumeRecordToFile(File fileRecord) {
    this.recordFile = fileRecord;
    isContinueRecordingToFile = true;
    isRecordingToFileEnabled = true;
    isRecordingToFilePrepared = false;
}

public void stopRecordToFile() {
    isRecordingToFileEnabled = false;
    isRecordingToFilePrepared = false;
    isContinueRecordingToFile = false;
}

public boolean stop() {
    boolean result = stopRecognizerThread();
    if (result) {
        this.mainHandler.post(new ResultEvent(this.recognizer.Rabcd(), true));
    }
    return result;
}

public boolean cancel() {
    boolean result = stopRecognizerThread();
    this.recognizer.Rabcd();
    return result;
}

public void shutdown() {
    this.recorder.release();
}

private final class RecognizerThread extends Thread {
    private static final int NO_TIMEOUT = -1;
    private int remainingSamples;
    private int timeoutSamples;
    VoiceRecorder voiceRecorder = null;

    public RecognizerThread(int timeout) {
        if (timeout != NO_TIMEOUT) {
            this.timeoutSamples = (SpeechRecognizer.SAMPLE_RATE * timeout) / 1000;
        } else {
            this.timeoutSamples = NO_TIMEOUT;
        }
        this.remainingSamples = this.timeoutSamples;
    }

    public RecognizerThread(SpeechRecognizer speechRecognizer) {
        this(NO_TIMEOUT);
    }

    public void run() {
        voiceRecorder = new VoiceRecorder();

        SpeechRecognizer.this.recorder.startRecording();
        if (SpeechRecognizer.this.recorder.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) {
            SpeechRecognizer.this.recorder.stop();
            SpeechRecognizer.this.mainHandler.post(new OnErrorEvent(new IOException("Failed to start recording. Microphone might be already in use.")));

            return;
        }

        byte[] buffer = new byte[SpeechRecognizer.this.bufferSize];

        while (!interrupted() && (this.timeoutSamples == NO_TIMEOUT || this.remainingSamples > 0)) {
            int nread = SpeechRecognizer.this.recorder.read(buffer, 0, buffer.length);

            if (soundAmplitudeCallback != null) {
                int max = 0;
                for (short s : buffer) {
                    if (Math.abs(s) > max) {
                        max = Math.abs(s);
                    }
                }
                soundAmplitudeCallback.onAmplitude(max);
            }

            if (nread < 0) {
                throw new RuntimeException("error reading audio buffer");
            }

            voiceRecorder.recording(nread, buffer);

            if (SpeechRecognizer.this.recognizer.Aabcd(buffer, nread)) {
                SpeechRecognizer.this.mainHandler.post(new ResultEvent(SpeechRecognizer.this.recognizer.Rabcd(), true));
            } else {
                SpeechRecognizer.this.mainHandler.post(new ResultEvent(SpeechRecognizer.this.recognizer.Pabcd(), false));
            }

            if (this.timeoutSamples != NO_TIMEOUT) {
                this.remainingSamples -= nread;
            }
        }

        voiceRecorder.shutdown();
        SpeechRecognizer.this.recorder.stop();

        SpeechRecognizer.this.mainHandler.removeCallbacksAndMessages((Object) null);
        if (this.timeoutSamples != NO_TIMEOUT && this.remainingSamples <= 0) {
            SpeechRecognizer.this.mainHandler.post(new TimeoutEvent());
        }
    }
}

/*
* Voice Recorder to file
* */
private class VoiceRecorder{
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    FileOutputStream fileOutputStream = null;
    MediaCodec mediaCodec = null;

    void recording(int nread, byte[] buffer){
        /*step 1 prepare file*/
        if (isRecordingToFileEnabled && !isRecordingToFilePrepared) {
            //if we continue recording not create new file
            if (recordFile == null) {
                throw new IllegalArgumentException("Record file is null");
            }

            try {
                fileOutputStream = new FileOutputStream(recordFile, isContinueRecordingToFile);
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }

            if (mediaCodec == null){
                try {
                    mediaCodec = createMediaCodec(bufferSize);
                    mediaCodec.start();
                    Timber.d("mediaCodec.start()");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            isRecordingToFilePrepared = true;
        }
        /*prepare file*/

        /*step 2 recording*/
        if (isRecordingToFileEnabled && isRecordingToFilePrepared) {
            try {
                if (fileOutputStream != null){
                    boolean success = handleCodecInput(nread, buffer, mediaCodec, Thread.currentThread().isAlive());
                    if (success)
                        handleCodecOutput(mediaCodec, bufferInfo, fileOutputStream);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        /*recording*/

        /*step 3 finish recording and save to file*/
        if (!isRecordingToFileEnabled && fileOutputStream != null) {
            try {
                VoiceRecorder.this.shutdown();

                fileOutputStream.flush();
                fileOutputStream.close();
                fileOutputStream = null;

                Timber.d("Finishing file");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        /*finish recording and save to file*/
    }

    void shutdown(){
        if (mediaCodec != null){
            mediaCodec.stop();
            mediaCodec.release();
            mediaCodec = null;
        }
    }
}

private abstract class RecognitionEvent implements Runnable {

    public abstract void execute(RecognitionListener recognitionListener);

    private RecognitionEvent() {
    }

    public void run() {
        for (RecognitionListener listener : (RecognitionListener[]) SpeechRecognizer.this.listeners.toArray(new RecognitionListener[0])) {
            execute(listener);
        }
    }
}

private class ResultEvent extends RecognitionEvent {
    private final boolean finalResult;
    protected final String hypothesis;

    ResultEvent(String hypothesis2, boolean finalResult2) {
        super();
        this.hypothesis = hypothesis2;
        this.finalResult = finalResult2;
    }


    public void execute(RecognitionListener listener) {
        if (this.finalResult) {
            listener.onResult(this.hypothesis);
        } else {
            listener.onPartialResult(this.hypothesis);
        }
    }
}

private class OnErrorEvent extends RecognitionEvent {
    private final Exception exception;

    OnErrorEvent(Exception exception2) {
        super();
        this.exception = exception2;
    }


    public void execute(RecognitionListener listener) {
        listener.onError(this.exception);
    }
}

private class TimeoutEvent extends RecognitionEvent {
    private TimeoutEvent() {
        super();
    }


    public void execute(RecognitionListener listener) {
        listener.onTimeout();
    }
}

private AudioRecord createAudioRecorder(int bufferSize) {
    AudioRecord recorder = new AudioRecord(MediaRecorder.AudioSource.VOICE_RECOGNITION, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize * 10);

    if (android.media.audiofx.NoiseSuppressor.isAvailable()) {
        android.media.audiofx.NoiseSuppressor noiseSuppressor = android.media.audiofx.NoiseSuppressor
                .create(recorder.getAudioSessionId());
        if (noiseSuppressor != null) {
            noiseSuppressor.setEnabled(true);
        }
    }


    if (android.media.audiofx.AutomaticGainControl.isAvailable()) {
        android.media.audiofx.AutomaticGainControl automaticGainControl = android.media.audiofx.AutomaticGainControl
                .create(recorder.getAudioSessionId());
        if (automaticGainControl != null) {
            automaticGainControl.setEnabled(true);
        }
    }

    return recorder;
}

private MediaCodec createMediaCodec(int bufferSize) throws IOException {
    MediaCodec mediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
    MediaFormat mediaFormat = new MediaFormat();

    mediaFormat.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
    mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, SAMPLE_RATE);
    mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNELS);
    mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize);
    mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
    mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);

    try {
        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    } catch (Exception e) {
        Timber.tag(TAG).w(e);
        mediaCodec.release();
        throw new IOException(e);
    }

    return mediaCodec;
}

private boolean handleCodecInput(int length,
                                 byte[] buffer,
                                 MediaCodec mediaCodec,
                                 boolean running) {

    if (length == AudioRecord.ERROR_BAD_VALUE ||
            length == AudioRecord.ERROR_INVALID_OPERATION ||
            length != bufferSize) {

        if (length != bufferSize) {
            Timber.tag(TAG).d( "length != BufferSize calling onRecordFailed");
//                if (onRecorderFailedListener != null) {
//                    Log.d(TAG, "length != BufferSize calling onRecordFailed");
//                    onRecorderFailedListener.onRecorderFailed();
//                }
            return false;
        }
    }

    int codecInputBufferIndex = mediaCodec.dequeueInputBuffer(10 * 1000);

    if (codecInputBufferIndex >= 0) {
        ByteBuffer codecBuffer = mediaCodec.getInputBuffer(codecInputBufferIndex);
        codecBuffer.clear();
        codecBuffer.put(buffer);
        mediaCodec.queueInputBuffer(codecInputBufferIndex, 0, length, 0, running ? 0 : MediaCodec.BUFFER_FLAG_END_OF_STREAM);
    }

    return true;
}

private void handleCodecOutput(MediaCodec mediaCodec,
                               MediaCodec.BufferInfo bufferInfo,
                               OutputStream outputStream) throws IOException {
    int codecOutputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);

    while (codecOutputBufferIndex != MediaCodec.INFO_TRY_AGAIN_LATER) {
        if (codecOutputBufferIndex >= 0) {
            ByteBuffer encoderOutputBuffer = mediaCodec.getOutputBuffer(codecOutputBufferIndex);

            encoderOutputBuffer.position(bufferInfo.offset);
            encoderOutputBuffer.limit(bufferInfo.offset + bufferInfo.size);

            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                byte[] header = createAdtsHeader(bufferInfo.size - bufferInfo.offset);


                outputStream.write(header);

                byte[] data = new byte[encoderOutputBuffer.remaining()];
                encoderOutputBuffer.get(data);
                outputStream.write(data);
            }

            encoderOutputBuffer.clear();

            mediaCodec.releaseOutputBuffer(codecOutputBufferIndex, false);
        }

        codecOutputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
    }
}

private byte[] createAdtsHeader(int length) {
    int frameLength = length + 7;
    byte[] adtsHeader = new byte[7];

    adtsHeader[0] = (byte) 0xFF; // Sync Word
    adtsHeader[1] = (byte) 0xF1; // MPEG-4, Layer (0), No CRC
    adtsHeader[2] = (byte) ((MediaCodecInfo.CodecProfileLevel.AACObjectLC - 1) << 6);
    adtsHeader[2] |= (((byte) SAMPLE_RATE_INDEX) << 2);
    adtsHeader[2] |= (((byte) CHANNELS) >> 2);
    adtsHeader[3] = (byte) (((CHANNELS & 3) << 6) | ((frameLength >> 11) & 0x03));
    adtsHeader[4] = (byte) ((frameLength >> 3) & 0xFF);
    adtsHeader[5] = (byte) (((frameLength & 0x07) << 5) | 0x1f);
    adtsHeader[6] = (byte) 0xFC;

    return adtsHeader;
}
}

于 2020-09-04T06:09:35.930 回答