【问题标题】:MediaCodec AV Sync when decoding解码时的 MediaCodec AV Sync
【发布时间】:2020-09-23 11:03:40
【问题描述】:

所有关于同步音频和视频的问题,在使用MediaCodec 解码时,都表明我们应该使用“AV Sync”机制使用它们的时间戳来同步视频和音频。

这是我为实现这一目标所做的:

我有 2 个线程,一个用于解码视频,一个用于音频。要同步视频和音频,我使用Extractor.getSampleTime() 来确定是否应该释放音频或视频缓冲区,请参见下文:

//This is called after configuring MediaCodec(both audio and video)
private void startPlaybackThreads(){
    //Audio playback thread
    mAudioWorkerThread = new Thread("AudioThread") {
        @Override
        public void run() {
            if (!Thread.interrupted()) {
                try {
                    //Check info below
                    if (shouldPushAudio()) {
                        workLoopAudio();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };
    mAudioWorkerThread.start();

    //Video playback thread
    mVideoWorkerThread = new Thread("VideoThread") {
        @Override
        public void run() {
            if (!Thread.interrupted()) {
                try {
                    //Check info below
                    if (shouldPushVideo()) {
                        workLoopVideo();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };
    mVideoWorkerThread.start();
}

//Check if more buffers should be sent to the audio decoder
private boolean shouldPushAudio(){
    int audioTime =(int) mAudioExtractor.getSampleTime();
    int videoTime = (int) mExtractor.getSampleTime();
    return audioTime <= videoTime;
}
//Check if more buffers should be sent to the video decoder
private boolean shouldPushVideo(){
    int audioTime =(int) mAudioExtractor.getSampleTime();
    int videoTime = (int) mExtractor.getSampleTime();
    return audioTime > videoTime;
}

workLoopAudio()workLoopVideo() 里面都是我的MediaCodec 逻辑(我决定不发布它,因为它不相关)。

所以我要做的是,获取视频和音轨的采样时间,然后检查哪个更大(更远)。如果视频“领先”,那么我将更多缓冲区传递给我的音频解码器,反之亦然。

这似乎工作正常 - 视频和音频正在同步播放。


我的问题:

我想知道我的方法是否正确(我们应该这样做,还是有另一种/更好的方法)?我找不到任何这样的工作示例(用 java/kotlin 编写),因此是这个问题。


编辑 1:

当我解码/播放使用FFmpeg 编码的视频时,我发现视频后面的音频拖尾(非常轻微)。如果我使用未使用FFmpeg 编码的视频,则视频和音频会完美同步。

FFmpeg 命令没有什么特别之处:

-i inputPath -crf 18 -c:v libx264 -preset ultrafast OutputPath

我将在下面提供更多信息:

我像这样初始化/创建AudioTrack

//Audio
mAudioExtractor = new MediaExtractor();
mAudioExtractor.setDataSource(mSource);
int audioTrackIndex = selectAudioTrack(mAudioExtractor);
if (audioTrackIndex < 0){
    throw new IOException("Can't find Audio info!");
}
mAudioExtractor.selectTrack(audioTrackIndex);
mAudioFormat = mAudioExtractor.getTrackFormat(audioTrackIndex);
mAudioMime = mAudioFormat.getString(MediaFormat.KEY_MIME);

mAudioChannels = mAudioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
mAudioSampleRate = mAudioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);

final int min_buf_size = AudioTrack.getMinBufferSize(mAudioSampleRate, (mAudioChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO), AudioFormat.ENCODING_PCM_16BIT);
final int max_input_size = mAudioFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
mAudioInputBufSize =  min_buf_size > 0 ? min_buf_size * 4 : max_input_size;
if (mAudioInputBufSize > max_input_size) mAudioInputBufSize = max_input_size;
final int frameSizeInBytes = mAudioChannels * 2;
mAudioInputBufSize = (mAudioInputBufSize / frameSizeInBytes) * frameSizeInBytes;

mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
    mAudioSampleRate,
    (mAudioChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO),
    AudioFormat.ENCODING_PCM_16BIT,
    AudioTrack.getMinBufferSize(mAudioSampleRate, mAudioChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT),
    AudioTrack.MODE_STREAM);

try {
    mAudioTrack.play();
} catch (final Exception e) {
    Log.e(TAG, "failed to start audio track playing", e);
    mAudioTrack.release();
    mAudioTrack = null;
}

我这样写信给AudioTrack

//Called from within workLoopAudio, when releasing audio buffers
if (bufferAudioIndex >= 0) {
    if (mAudioBufferInfo.size > 0) {
        internalWriteAudio(mAudioOutputBuffers[bufferAudioIndex], mAudioBufferInfo.size);
    }
    mAudioDecoder.releaseOutputBuffer(bufferAudioIndex, false);
}

private boolean internalWriteAudio(final ByteBuffer buffer, final int size) {
    if (mAudioOutTempBuf.length < size) {
        mAudioOutTempBuf = new byte[size];
    }
    buffer.position(0);
    buffer.get(mAudioOutTempBuf, 0, size);
    buffer.clear();
    if (mAudioTrack != null)
        mAudioTrack.write(mAudioOutTempBuf, 0, size);
    return true;
}

“新”问题:

如果我使用使用FFmpeg 编码的视频,音频会在视频后面落后约 200 毫秒,是否有原因导致这种情况发生?

【问题讨论】:

    标签: java android ffmpeg android-mediacodec android-ffmpeg


    【解决方案1】:

    它现在似乎正在工作。我使用与上述相同的逻辑,但现在我保留了从 MediaCodec.BufferInfo() 返回的 presentationTimeUs 的引用,然后再调用 dequeueOutputBuffer 以检查是否应该继续我的视频或音频工作循环:

    // Check if audio work loop should continue
    private boolean shouldPushAudio(){
        long videoTime = mExtractor.getSampleTime();
        return tempAudioPresentationTimeUs <= videoTime;
    }
    // Check if video work loop should continue
    private boolean shouldPushVideo(){
        long videoTime = mExtractor.getSampleTime();
        return tempAudioPresentationTimeUs >= videoTime;
    }
    
    // tempAudioPresentationTimeUs is set right before I call dequeueOutputBuffer
    // As shown here:
    tempAudioPresentationTimeUs = mAudioBufferInfo.presentationTimeUs;
    int outIndex = mAudioDecoder.dequeueOutputBuffer(mAudioBufferInfo, timeout);
    
    

    通过这样做,我的视频和音频可以完美同步,即使是使用FFmpeg 编码的文件(正如我在上面的编辑中提到的)。


    我遇到了一个问题,我的视频工作循环没有完成,这是由于音频在视频之前到达 EOS,然后返回 -1。于是我把原来的mVideoWorkerThread改成了如下:

    mVideoWorkerThread = new Thread("VideoThread") {
        @Override
        public void run() {
            if (!Thread.interrupted()) {
                try {
                    if (shouldPushVideo() || audioReachedEOS()) {
                        workLoopVideo();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };
    mVideoWorkerThread.start();
    
    private boolean audioReachedEOS() {
        return mAudioExtractor.getSampleTime() == -1;
    }
    

    所以我使用audioReachedEOS() 来检查我的音频MediaExtractor 是否返回-1。如果是,则表示我的音频已完成,但我的视频未完成,因此我继续我的视频工作循环,直到完成。

    这似乎按预期工作(当我只播放/暂停视频而不搜索时)。我在寻找时遇到了另一个问题,但我不会详细说明。

    我将按原样发布我的应用程序,并在遇到问题时更新此答案。

    【讨论】:

      猜你喜欢
      • 2020-05-27
      • 2016-09-06
      • 2016-08-31
      • 2013-04-25
      • 1970-01-01
      • 2021-08-30
      • 1970-01-01
      • 2018-10-17
      • 1970-01-01
      相关资源
      最近更新 更多