gpt4 book ai didi

java - MediaCodec 解码时 AV 同步

转载 作者:行者123 更新时间:2023-12-01 16:23:59 27 4
gpt4 key购买 nike

使用 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;
}

"new"问题:

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

最佳答案

看起来现在可以工作了。我使用与上面相同的逻辑,但现在在调用 dequeueOutputBuffer 进行检查之前,我保留从 MediaCodec.BufferInfo() 返回的 presentationTimeUs 的引用如果我应该继续我的视频或音频工作循环:

// 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。如果是这样,则意味着我的音频已完成,但我的视频尚未完成,因此我继续我的视频工作循环,直到完成。

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

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

关于java - MediaCodec 解码时 AV 同步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62195022/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com