草庐IT

c++ - 媒体基础视频重新编码产生音频流同步偏移

coder 2024-06-14 原文

我正在尝试编写一个简单的 Windows 媒体基础命令行工具,以使用 IMFSourceReaderIMFSyncWriter 加载视频,读取视频和音频作为未压缩的流并使用一些特定的硬编码设置将它们重新编码为 H.246/AAC。

The simple program Gist is here

sample video 1

sample video 2

sample video 3

(注意:我一直在测试的视频都是立体声,48000k 采样率)

该程序可以运行,但在某些情况下,当在编辑程序中将新输出的视频与原始视频进行比较时,我发现复制的视频流匹配,但拷贝的音频流预先固定了一些静音并且音频偏移,这在我的情况下是 Not Acceptable 。

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)!

在将音频样本数据传递给 IMFSinkWriter 时,我已经能够修复此同步对齐并偏移视频流以从 0 开始:

//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

解决方案是在音频流的开头插入 block 大小为 1024 的额外静音IMFSourceReader 提供的音频 block 大小无关紧要,填充是 1024 的倍数。

我的问题是,静音偏移似乎没有可检测的原因。为什么我需要它?我怎么知道我需要多少?经过数天的解决这个问题后,我偶然发现了 1024 样本静音 block 解决方案。

有些视频似乎只需要 1 个填充 block ,有些需要 2 个或更多,有些根本不需要额外的填充!

我的问题是:

  • 有谁知道为什么会这样?

  • 是不是我在这种情况下错误地使用了 Media Foundation 导致了这种情况?

  • 如果我是正确的,我如何使用视频元数据来确定我是否需要填充音频流以及填充中需要多少 1024 个静音 block ?

编辑:

对于上面的示例视频:

  • sample video 1 : 视频流从 0 开始并且不需要额外的 block ,原始数据的直通工作正常。

  • sample video 2 : 视频流从 834166 (hns) 开始,需要 1 1024 block 静默来同步

  • sample video 3 : 视频流从 0 开始,需要 2 1024 个静音 block 才能同步。

更新:

我尝试过的其他事情:

  • 增加第一个视频帧的持续时间以解决偏移:不会产生任何效果。

最佳答案

我写了另一个版本的程序来正确处理 NV12 格式(你的不工作):

EncodeWithSourceReaderSinkWriter

我使用 Blender 作为视频编辑工具。这是我使用 Tuning_against_a_window.mov 的结果:

从下到上:

  • 原始文件
  • 编码文件
  • 我通过将“elst”原子设置为数字条目的值 0 来更改原始文件(我使用 Visual Studio hexa 编辑器)

正如 Roman R. 所说,MediaFoundation mp4 源不使用“edts/elst”原子。但是 Blender 和您的视频编辑工具可以。 mp4 源也会忽略“tmcd”轨道。

“edts/elst”:

Edits Atom ( 'edts' )

Edit lists can be used for hint tracks...

MPEG-4 File Source

The MPEG-4 file source silently ignores hint tracks.

所以其实编码是好的。与真实的音频/视频数据相比,我认为没有音频流同步偏移。例如,您可以将“edts/elst”添加到编码文件中,以获得相同的结果。

PS:在编码文件中,我为音频/视频轨道添加了“edts/elst”。我还增加了 trak 原子和 moov 原子的大小。我确认,Blender 为原始文件和编码文件显示相同的波形。

编辑

我试图在 3 个视频示例中了解 mvhd/tkhd/mdhd/elst 原子之间的关系。 (是的,我知道,我应该阅读规范。但我很懒...)

您可以使用 mp4 资源管理器工具获取原子的值,或使用我的 H264Dxva2Decoder 项目中的 mp4 解析器:

H264Dxva2Decoder

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。这就是此文件没有视频/音频同步问题的原因。

对于 Tuning_against_a_window.mov 和 GREEN_SCREEN_ANIMALS__ALPACA.mp4,我尝试计算视频/音频偏移。 我修改了我的项目以考虑到这一点:

EncodeWithSourceReaderSinkWriter

目前,我没有找到适用于所有文件的通用计算方法。

我只是找到了正确编码这两个文件所需的视频/音频偏移量。

对于 Tuning_against_a_window.mov,我在(电影时间 - 视频/音频 mdhd 时间)之后开始编码。 对于 GREEN_SCREEN_ANIMALS__ALPACA.mp4,我在视频/音频最后一个媒体时间后开始编码。

没关系,但我需要为所有文件找到正确的唯一计算。

所以你有两个选择:

  • 编码文件并添加elst atom
  • 使用右偏移计算对文件进行编码

这取决于你的需求:

  • 第一个选项允许您保留原始文件。但是您必须添加最后一个原子
  • 使用第二个选项,您必须在编码之前从文件中读取原子,并且编码后的文件将丢失一些原始帧

如果您选择第一个选项,我将解释如何添加第一个原子。

PS:我对这个问题很感兴趣,因为在我的 H264Dxva2Decoder 项目中,edts/elst 原子在我的待办事项列表中。 我解析它,但我不使用它...

PS2:这个链接听起来很有趣: Audio Priming - Handling Encoder Delay in AAC

关于c++ - 媒体基础视频重新编码产生音频流同步偏移,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55054531/

有关c++ - 媒体基础视频重新编码产生音频流同步偏移的更多相关文章

  1. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  2. ruby - 用逗号、双引号和编码解析 csv - 2

    我正在使用ruby​​1.9解析以下带有MacRoman字符的csv文件#encoding:ISO-8859-1#csv_parse.csvName,main-dialogue"Marceu","Giveittohimóhe,hiswife."我做了以下解析。require'csv'input_string=File.read("../csv_parse.rb").force_encoding("ISO-8859-1").encode("UTF-8")#=>"Name,main-dialogue\r\n\"Marceu\",\"Giveittohim\x97he,hiswife.\"\

  3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  4. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  5. ruby-on-rails - active_admin 目录中的常量警告重新声明 - 2

    我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA

  6. ruby - 在 Ruby 中重新分配常量时抛出异常? - 2

    我早就知道Ruby中的“常量”(即大写的变量名)不是真正常量。与其他编程语言一样,对对象的引用是唯一存储在变量/常量中的东西。(侧边栏:Ruby确实具有“卡住”引用对象不被修改的功能,据我所知,许多其他语言都没有提供这种功能。)所以这是我的问题:当您将一个值重新分配给常量时,您会收到如下警告:>>FOO='bar'=>"bar">>FOO='baz'(irb):2:warning:alreadyinitializedconstantFOO=>"baz"有没有办法强制Ruby抛出异常而不是打印警告?很难弄清楚为什么有时会发生重新分配。 最佳答案

  7. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  8. 怎样用一台手机做自媒体? - 2

    其实做自媒体的成本并不高,入门只需要一部手机即可!在手机上找视频素材、使用手机剪辑视频、最后使用手机发布视频作品获得收益!方法并不难,今天这期内容就来给粉丝们分享一种小方法,每天稳定收益100-300,抓紧点赞收藏!1、找素材(1)使用手机拍摄自己喜欢的经典段落,使用程序把文案内容提取出来(2)也可以在豆瓣、知乎、微博等网站中找一些自己需要的文案素材(3)把文案进行润色修改,可以加入一些自己的观点(4)视频素材可以使用软件中自带的素材,也可以在素材网站中下载完整版的素材2、文案配音(1)把复制好的文案直接导入小程序中(2)调整音色、音调后一键合成音频即可(3)可以选择自己朗读配音,需要花一点时

  9. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  10. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

随机推荐