媒体基础视频重新编码产生音频流同步偏移
Media Foundation video re-encoding producing audio stream sync offset
我正在尝试编写一个简单的 windows 媒体基础命令行工具来使用 IMFSourceReader
和 IMFSyncWriter
加载视频,读取视频和音频作为未压缩的流并使用一些特定的硬编码设置将它们重新编码为 H.246/AAC。
The simple program Gist is here
(注意:我一直在测试的视频都是立体声,48000k 采样率)
该程序有效,但在某些情况下,在编辑程序中将新输出的视频与原始视频进行比较时,我发现复制的视频流匹配,但副本的音频流预先固定了一些量静音和音频偏移,这在我的情况下是不可接受的。
audio samples:
original - |[audio1] [audio2] [audio3] [audio4] [audio5] ... etc
copy - |[silence] [silence] [silence] [audio1] [audio2] [audio3] ... etc
在这种情况下,第一个视频帧的时间戳为 非零,但第一个音频帧的时间戳为 0。
我希望能够复制视频和音频流的第一帧为 0 的视频,因此我首先尝试从所有后续视频帧中减去初始时间戳 (videoOffset
)制作了我想要的视频,但导致音频出现这种情况:
original - |[audio1] [audio2] [audio3] [audio4] [audio5] ... etc
copy - |[audio4] [audio5] [audio6] [audio7] [audio8] ... etc
音轨现在向另一个方向移动了一点,但仍然没有对齐。当视频流 的起始时间戳为 0 但 WMF 仍然在开头切断一些音频样本时,有时也会发生这种情况(参见示例视频 3)!
我已经能够修复此同步对齐并偏移视频流以从 0 开始,并在将音频样本数据传递到 IMFSinkWriter
时插入以下代码:
//inside read sample while loop
...
// LONGLONG llDuration has the currently read sample duration
// DWORD audioOffset has the global audio offset, starts as 0
// LONGLONG audioFrameTimestamp has the currently read sample timestamp
//add some random amount of silence in intervals of 1024 samples
static bool runOnce{ false };
if (!runOnce)
{
size_t numberOfSilenceBlocks = 1; //how to derive how many I need!? It's aribrary
size_t samples = 1024 * numberOfSilenceBlocks;
audioOffset = samples * 10000000 / audioSamplesPerSecond;
std::vector<uint8_t> silence(samples * audioChannels * bytesPerSample, 0);
WriteAudioBuffer(silence.data(), silence.size(), audioFrameTimeStamp, audioOffset);
runOnce= true;
}
LONGLONG audioTime = audioFrameTimeStamp + audioOffset;
WriteAudioBuffer(dataPtr, dataSize, audioTime, llDuration);
奇怪的是,这会创建一个与原始文件相匹配的输出视频文件。
original - |[audio1] [audio2] [audio3] [audio4] [audio5] ... etc
copy - |[audio1] [audio2] [audio3] [audio4] [audio5] ... etc
解决方案是在音频流的开头插入 额外的静音 ,块大小为 1024。 IMFSourceReader
提供的音频块大小无关紧要,填充是 1024 的倍数。
我的问题是,静音偏移似乎没有可检测的原因。为什么我需要它?我怎么知道我需要多少?经过数天的解决这个问题后,我偶然发现了 1024 样本静音块解决方案。
有些视频似乎只需要 1 个填充块,有些需要 2 个或更多,有些根本不需要额外的填充!
我的问题是:
有谁知道为什么会这样?
是不是我在这种情况下错误地使用了 Media Foundation 导致了这种情况?
如果我是正确的,我如何使用视频元数据来确定我是否需要填充音频流以及填充中需要多少 1024 个静音块?
编辑:
对于上面的示例视频:
sample video 1:视频流从 0 开始,不需要额外的块,原始数据的直通工作正常。
sample video 2 : 视频流从 834166 (hns) 开始,需要 1 1024 个静默块来同步
sample video 3 : 视频流从 0 开始,需要 2 1024 个静默块来同步。
更新:
我尝试过的其他事情:
- 增加第一个视频帧的持续时间以解决偏移:没有效果。
我写了另一个版本的程序来正确处理 NV12 格式(你的不工作):
EncodeWithSourceReaderSinkWriter
我使用 Blender 作为视频编辑工具。这是 Tuning_against_a_window.mov 的结果:
从下到上:
- 原始文件
- 编码文件
- 我通过设置 "elst" atoms 更改了原始文件,其中数字条目的值为 0(我使用 Visual Studio hex 编辑器)
正如 Roman R. 所说,MediaFoundation mp4 源不使用 "edts/elst" 原子。但是 Blender 和您的视频编辑工具可以。 "tmcd" 曲目也被 mp4 源忽略。
"edts/elst" :
Edit lists can be used for hint tracks...
The MPEG-4 file source silently ignores hint tracks.
所以其实编码是好的。与真实的 audio/video 数据相比,我认为没有音频流同步偏移。例如,您可以将 "edts/elst" 添加到编码文件中,以获得相同的结果。
PS:在编码文件中,我为两个 audio/video 轨道添加了 "edts/elst"。我还增加了 trak 原子和 moov 原子的大小。我确认,Blender 对原始文件和编码文件显示相同的波形。
编辑
我试图了解 3 个视频样本中 mvhd/tkhd/mdhd/elst 个原子之间的关系。 (是的,我知道,我应该阅读规范。但我很懒...)
您可以使用 mp4 浏览器工具获取 atom 的值,或使用我的 H264Dxva2Decoder 项目中的 mp4 解析器:
Tuning_against_a_window.mov
- 来自 tkhd 视频的最后(媒体时间):20689
- 来自 tkhd 音频的最后(媒体时间):1483
GREEN_SCREEN_ANIMALS__ALPACA.mp4
- tkhd 视频的最后(媒体时间):2002
- 来自 tkhd 音频的最后(媒体时间):1024
GOPR6239_1.mov
- 来自 tkhd 视频的最后(媒体时间):0
- 来自 tkhd 音频的最后(媒体时间):0
如您所见,对于 GOPR6239_1.mov,elst 的媒体时间为 0。这就是为什么此文件没有 video/audio 同步问题。
对于 Tuning_against_a_window.mov 和 GREEN_SCREEN_ANIMALS__ALPACA.mp4,我试图计算 video/audio 偏移量。
我修改了我的项目以考虑到这一点:
EncodeWithSourceReaderSinkWriter
目前,我没有找到适用于所有文件的通用计算方法。
我刚找到正确编码两个文件所需的 video/audio 偏移量。
对于Tuning_against_a_window.mov,我在(电影时间 - video/audio mdhd 时间)之后开始编码。
对于 GREEN_SCREEN_ANIMALS__ALPACA.mp4,我在 video/audio 上一个媒体时间后开始编码。
没关系,但我需要为所有文件找到正确的唯一计算。
所以你有两个选择:
- 编码文件并添加最后一个原子
- 使用右偏移计算对文件进行编码
这取决于您的需求:
- 第一个选项允许你保留原来的file.But你必须添加最后一个原子
- 使用第二个选项,您必须在编码之前从文件中读取 atom,编码后的文件将丢失一些原始帧
如果您选择第一个选项,我将解释如何添加第一个原子。
PS :我对这个问题很感兴趣,因为在我的 H264Dxva2Decoder 项目中,edts/elst 原子在我的待办事项列表中。
我解析它,但我不使用它...
PS2:这个 link 听起来很有趣:
Audio Priming - Handling Encoder Delay in AAC
我正在尝试编写一个简单的 windows 媒体基础命令行工具来使用 IMFSourceReader
和 IMFSyncWriter
加载视频,读取视频和音频作为未压缩的流并使用一些特定的硬编码设置将它们重新编码为 H.246/AAC。
The simple program Gist is here
(注意:我一直在测试的视频都是立体声,48000k 采样率)
该程序有效,但在某些情况下,在编辑程序中将新输出的视频与原始视频进行比较时,我发现复制的视频流匹配,但副本的音频流预先固定了一些量静音和音频偏移,这在我的情况下是不可接受的。
audio samples:
original - |[audio1] [audio2] [audio3] [audio4] [audio5] ... etc
copy - |[silence] [silence] [silence] [audio1] [audio2] [audio3] ... etc
在这种情况下,第一个视频帧的时间戳为 非零,但第一个音频帧的时间戳为 0。
我希望能够复制视频和音频流的第一帧为 0 的视频,因此我首先尝试从所有后续视频帧中减去初始时间戳 (videoOffset
)制作了我想要的视频,但导致音频出现这种情况:
original - |[audio1] [audio2] [audio3] [audio4] [audio5] ... etc
copy - |[audio4] [audio5] [audio6] [audio7] [audio8] ... etc
音轨现在向另一个方向移动了一点,但仍然没有对齐。当视频流 的起始时间戳为 0 但 WMF 仍然在开头切断一些音频样本时,有时也会发生这种情况(参见示例视频 3)!
我已经能够修复此同步对齐并偏移视频流以从 0 开始,并在将音频样本数据传递到 IMFSinkWriter
时插入以下代码:
//inside read sample while loop
...
// LONGLONG llDuration has the currently read sample duration
// DWORD audioOffset has the global audio offset, starts as 0
// LONGLONG audioFrameTimestamp has the currently read sample timestamp
//add some random amount of silence in intervals of 1024 samples
static bool runOnce{ false };
if (!runOnce)
{
size_t numberOfSilenceBlocks = 1; //how to derive how many I need!? It's aribrary
size_t samples = 1024 * numberOfSilenceBlocks;
audioOffset = samples * 10000000 / audioSamplesPerSecond;
std::vector<uint8_t> silence(samples * audioChannels * bytesPerSample, 0);
WriteAudioBuffer(silence.data(), silence.size(), audioFrameTimeStamp, audioOffset);
runOnce= true;
}
LONGLONG audioTime = audioFrameTimeStamp + audioOffset;
WriteAudioBuffer(dataPtr, dataSize, audioTime, llDuration);
奇怪的是,这会创建一个与原始文件相匹配的输出视频文件。
original - |[audio1] [audio2] [audio3] [audio4] [audio5] ... etc
copy - |[audio1] [audio2] [audio3] [audio4] [audio5] ... etc
解决方案是在音频流的开头插入 额外的静音 ,块大小为 1024。 IMFSourceReader
提供的音频块大小无关紧要,填充是 1024 的倍数。
我的问题是,静音偏移似乎没有可检测的原因。为什么我需要它?我怎么知道我需要多少?经过数天的解决这个问题后,我偶然发现了 1024 样本静音块解决方案。
有些视频似乎只需要 1 个填充块,有些需要 2 个或更多,有些根本不需要额外的填充!
我的问题是:
有谁知道为什么会这样?
是不是我在这种情况下错误地使用了 Media Foundation 导致了这种情况?
如果我是正确的,我如何使用视频元数据来确定我是否需要填充音频流以及填充中需要多少 1024 个静音块?
编辑:
对于上面的示例视频:
sample video 1:视频流从 0 开始,不需要额外的块,原始数据的直通工作正常。
sample video 2 : 视频流从 834166 (hns) 开始,需要 1 1024 个静默块来同步
sample video 3 : 视频流从 0 开始,需要 2 1024 个静默块来同步。
更新:
我尝试过的其他事情:
- 增加第一个视频帧的持续时间以解决偏移:没有效果。
我写了另一个版本的程序来正确处理 NV12 格式(你的不工作):
EncodeWithSourceReaderSinkWriter
我使用 Blender 作为视频编辑工具。这是 Tuning_against_a_window.mov 的结果:
从下到上:
- 原始文件
- 编码文件
- 我通过设置 "elst" atoms 更改了原始文件,其中数字条目的值为 0(我使用 Visual Studio hex 编辑器)
正如 Roman R. 所说,MediaFoundation mp4 源不使用 "edts/elst" 原子。但是 Blender 和您的视频编辑工具可以。 "tmcd" 曲目也被 mp4 源忽略。
"edts/elst" :
Edit lists can be used for hint tracks...
The MPEG-4 file source silently ignores hint tracks.
所以其实编码是好的。与真实的 audio/video 数据相比,我认为没有音频流同步偏移。例如,您可以将 "edts/elst" 添加到编码文件中,以获得相同的结果。
PS:在编码文件中,我为两个 audio/video 轨道添加了 "edts/elst"。我还增加了 trak 原子和 moov 原子的大小。我确认,Blender 对原始文件和编码文件显示相同的波形。
编辑
我试图了解 3 个视频样本中 mvhd/tkhd/mdhd/elst 个原子之间的关系。 (是的,我知道,我应该阅读规范。但我很懒...)
您可以使用 mp4 浏览器工具获取 atom 的值,或使用我的 H264Dxva2Decoder 项目中的 mp4 解析器:
Tuning_against_a_window.mov
- 来自 tkhd 视频的最后(媒体时间):20689
- 来自 tkhd 音频的最后(媒体时间):1483
GREEN_SCREEN_ANIMALS__ALPACA.mp4
- tkhd 视频的最后(媒体时间):2002
- 来自 tkhd 音频的最后(媒体时间):1024
GOPR6239_1.mov
- 来自 tkhd 视频的最后(媒体时间):0
- 来自 tkhd 音频的最后(媒体时间):0
如您所见,对于 GOPR6239_1.mov,elst 的媒体时间为 0。这就是为什么此文件没有 video/audio 同步问题。
对于 Tuning_against_a_window.mov 和 GREEN_SCREEN_ANIMALS__ALPACA.mp4,我试图计算 video/audio 偏移量。 我修改了我的项目以考虑到这一点:
EncodeWithSourceReaderSinkWriter
目前,我没有找到适用于所有文件的通用计算方法。
我刚找到正确编码两个文件所需的 video/audio 偏移量。
对于Tuning_against_a_window.mov,我在(电影时间 - video/audio mdhd 时间)之后开始编码。 对于 GREEN_SCREEN_ANIMALS__ALPACA.mp4,我在 video/audio 上一个媒体时间后开始编码。
没关系,但我需要为所有文件找到正确的唯一计算。
所以你有两个选择:
- 编码文件并添加最后一个原子
- 使用右偏移计算对文件进行编码
这取决于您的需求:
- 第一个选项允许你保留原来的file.But你必须添加最后一个原子
- 使用第二个选项,您必须在编码之前从文件中读取 atom,编码后的文件将丢失一些原始帧
如果您选择第一个选项,我将解释如何添加第一个原子。
PS :我对这个问题很感兴趣,因为在我的 H264Dxva2Decoder 项目中,edts/elst 原子在我的待办事项列表中。 我解析它,但我不使用它...
PS2:这个 link 听起来很有趣: Audio Priming - Handling Encoder Delay in AAC