【问题标题】:Unable to mux both audio and video无法混合音频和视频
【发布时间】:2014-02-01 09:43:16
【问题描述】:

我正在编写一个使用 MediaCodec 记录屏幕截图和音频的应用程序。我使用 MediaMuxer 来混合视频和音频以创建 mp4 文件。我成功地分别编写了视频和音频,但是当我尝试将它们混合在一起时,结果出乎意料。要么在没有视频的情况下播放音频,要么在音频之后立即播放视频。我的猜测是我在时间戳方面做错了,但我无法弄清楚到底是什么。我已经查看了这些示例:https://github.com/OnlyInAmerica/HWEncoderExperiments/tree/audiotest/HWEncoderExperiments/src/main/java/net/openwatch/hwencoderexperiments 和 bigflake.com 上的示例,但无法找到答案。

这是我的媒体格式配置:

    mVideoFormat = createMediaFormat();

    private static MediaFormat createVideoFormat() {
    MediaFormat format = MediaFormat.createVideoFormat(
            Preferences.MIME_TYPE, mScreenWidth, mScreenHeight);
    format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
            MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
    format.setInteger(MediaFormat.KEY_BIT_RATE, Preferences.BIT_RATE);
    format.setInteger(MediaFormat.KEY_FRAME_RATE, Preferences.FRAME_RATE);
    format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,
            Preferences.IFRAME_INTERVAL);
    return format;
}
    mAudioFormat = createAudioFormat();

    private static MediaFormat createAudioFormat() {
    MediaFormat format = new MediaFormat();
    format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
    format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
    format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
    format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
    format.setInteger(MediaFormat.KEY_BIT_RATE, 64000);
    return format;
}

音视频编码器、复用器:

      mVideoEncoder = MediaCodec.createEncoderByType(Preferences.MIME_TYPE);
    mVideoEncoder.configure(mVideoFormat, null, null,
            MediaCodec.CONFIGURE_FLAG_ENCODE);
    mInputSurface = new InputSurface(mVideoEncoder.createInputSurface(),
            mSavedEglContext);
    mVideoEncoder.start();
    if (recordAudio){
        audioBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_CONFIGURATION_MONO, 
        AudioFormat.ENCODING_PCM_16BIT);
        mAudioRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100,
        AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, audioBufferSize);
        mAudioRecorder.startRecording();

        mAudioEncoder = MediaCodec.createEncoderByType("audio/mp4a-latm");
        mAudioEncoder.configure(mAudioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mAudioEncoder.start();
    }
    try {
        String fileId = String.valueOf(System.currentTimeMillis());
        mMuxer = new MediaMuxer(dir.getPath() + "/Video"
                + fileId + ".mp4",
                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
    } catch (IOException ioe) {
        throw new RuntimeException("MediaMuxer creation failed", ioe);
    }
    mVideoTrackIndex = -1;
    mAudioTrackIndex = -1;
    mMuxerStarted = false;

我用它来设置视频时间戳:

mInputSurface.setPresentationTime(mSurfaceTexture.getTimestamp());
drainVideoEncoder(false);

这是设置音频时间戳:

lastQueuedPresentationTimeStampUs = getNextQueuedPresentationTimeStampUs();

if(endOfStream)
    mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, audioBuffer.length, lastQueuedPresentationTimeStampUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
 else
     mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, audioBuffer.length, lastQueuedPresentationTimeStampUs, 0);


  mAudioBufferInfo.presentationTimeUs = getNextDeQueuedPresentationTimeStampUs();
  mMuxer.writeSampleData(mAudioTrackIndex, encodedData,
                           mAudioBufferInfo);
  lastDequeuedPresentationTimeStampUs = mAudioBufferInfo.presentationTimeUs;


  private static long getNextQueuedPresentationTimeStampUs(){
    long nextQueuedPresentationTimeStampUs = (lastQueuedPresentationTimeStampUs > lastDequeuedPresentationTimeStampUs) 
            ? (lastQueuedPresentationTimeStampUs + 1) : (lastDequeuedPresentationTimeStampUs + 1);
    Log.i(TAG, "nextQueuedPresentationTimeStampUs: " + nextQueuedPresentationTimeStampUs);
    return nextQueuedPresentationTimeStampUs;
}


private static long getNextDeQueuedPresentationTimeStampUs(){
    Log.i(TAG, "nextDequeuedPresentationTimeStampUs: " + (lastDequeuedPresentationTimeStampUs + 1));
    lastDequeuedPresentationTimeStampUs ++;
    return lastDequeuedPresentationTimeStampUs;
}

为了避免出现“timestampUs XXX https://github.com/OnlyInAmerica/HWEncoderExperiments/blob/audiotest/HWEncoderExperiments/src/main/java/net/openwatch/hwencoderexperiments/AudioEncodingTest.java

有人可以帮我解决问题吗?

【问题讨论】:

  • 嘿@Alexey,你能同时获得音频和视频吗??

标签: android audio video h.264 android-mediacodec


【解决方案1】:

您似乎在使用系统提供的视频时间戳,但使用简单的音频计数器。除非以某种方式使用视频时间戳来为每一帧的音频播种,并且它只是没有在上面显示。

要同步播放音频和视频,您需要在预期同时呈现的音频和视频帧上具有相同的呈现时间戳。

另请参阅related question

【讨论】:

  • 非常感谢fadden,我可以同步音频和视频了!你太棒了:)
  • 我还有一个小问题。我的录音在播放时播放音频比没有音频更快。并且音轨似乎失真了。我认为这是因为音频是在每个视频帧中逐块写入的。要写入的音频块的大小取决于 fps 和采样率。由于我的 fps 变化很大,因此音频是由不相等的块写入的,因此会产生失真。也许您对如何处理有什么建议?
  • 不幸的是,我在音频方面做得不多。音频时间戳从哪里来?您有一个很好的视频时间戳来源(SurfaceTexture 时间戳)。您应该对音频使用类似的东西,或者用System.nanoTime() 伪造它。 (媒体的东西一般使用系统单调时钟,和System.nanoTime()一样,虽然有些部分使用微秒而不是纳秒。)
  • 我的音频和视频时间戳都来自 System.nanoTime()。我不认为时间戳现在是问题,问题在于不同的音频块大小。我将尝试找到一种方法来以相同的间隔排出音频编码器(不是在 onDrawFrame 方法中,因为它现在已经完成了)。如果您对此有任何想法,非常欢迎您:)
  • 我不确定如何最好地解决这个问题。您可能应该将其完整详细地写成一个新的 stackoverflow 问题。
【解决方案2】:

我认为解决方案可能是重复读取音频样本。您可以检查每 N 个音频样本是否有一个新的视频帧可用,并在新的视频帧到达后立即将其传递给具有相同时间戳的复用器。

int __buffer_offset = 0;
final int CHUNK_SIZE = 100; /* record 100 samples each iteration */
while (!__new_video_frame_available) {
    this._audio_recorder.read(__recorded_data, __buffer_offset, CHUNK_SIZE);
    __buffer_offset += CHUNK_SIZE;
}

我认为应该可以。

最诚挚的问候, 沃尔夫拉姆

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-04
    • 2012-11-19
    • 2013-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多