mp4文件elst研究

elst也就是Edit List Box,不是所有的mp4文件有这个box,作用是使某个track的时间戳产生偏移。

结构

在ISO_IEC_14496-12中,elst结构如下:
mp4文件elst研究 - 第1张  | Jianchihu
segment_duration:表示该edit段的时长,以Movie Header Box(mvhd)中的timescale为单位。
media_time:表示该edit段的起始时间,以track中Media Header Box(mdhd)中的timescale为单位。如果值为-1,表示是空edit,一个track中最后一个edit不能为空。
media_rate:edit段的速率为0的话,edit段相当于一个”dwell”,即画面停止。画面会在media_time点上停止segment_duration时间。否则这个值始终为1。

例子

现在我们手里有个mp4文件,我们要让封装的视频延迟10秒才开始显示,封装的音频不变,这个可以通过修改视频的时间戳实现,将视频的所有时间戳都加上10秒,但是一个个改太麻烦了,此时elst就派上用场了,我们要通过它让视频时间戳偏移10秒。

首先我们先动手操作番,了解elst如何起作用。

1)找一个没有elst box的mp4文件:test.mp4,假设我放在D:\\bin目录下。至于mp4有没有包含elst可以用文章末尾链接提供的mp4分析工具Mp4Reader分析下。用mediainfo查看该视频的信息:
mp4文件elst研究 - 第2张  | Jianchihu
该mp4音视频时长都为3分32秒。

2)得到带elst的mp4。到https://gpac.wp.mines-telecom.fr/mp4box/ 下载windows下的mp4box,按提示一步步安装。
打开windows cmd命令行,cd到test.mp4目录,然后敲入Mp4Box的命令:

C:\Users\MyPc>D: D:\>cd bin D:\bin>Mp4Box -info test.mp4

1

2

3

4

5

C:\Users\MyPc>D:

D:\>cd bin

D:\bin>Mp4Box -info test.mp4

得到:
mp4文件elst研究 - 第3张  | Jianchihu
由此可知test.mp4中,视频的track id 为1,音频track id为2。

3)接着我们敲如下命令:

D:\bin>MP4Box -add test.mp4#1:delay=10000 -add test.mp4#2 delay_10s.mp4

1

D:\bin>MP4Box -add test.mp4#1:delay=10000 -add test.mp4#2 delay_10s.mp4

由于我们只对视频操作,视频track id是1,所以是#1:delay=10000。得到:
mp4文件elst研究 - 第4张  | Jianchihu
此时在bin目录下会生成一个delay_10s.mp4,该mp4中视频track延迟了10秒,音频track不变。mediainfo查看delay_10s.mp4信息:
mp4文件elst研究 - 第5张  | Jianchihu
可以看到视频的时长多了10秒

4)打开mp4reader查看视频track的elst信息:
mp4文件elst研究 - 第6张  | Jianchihu
也就是:

Entry-count = 2 Segment-duration = 10秒,  Media-Time = -1 Media-Rate = 1 Segment-duration = 3分32秒 Media-Time = 0 Media-Rate = 1

1

2

3

4

5

6

7

8

Entry-count = 2

Segment-duration = 10 秒

Media-Time = -1

Media-Rate = 1

Segment-duration = 3分32秒

Media-Time = 0

Media-Rate = 1

可以看到有两个elst entry,第一个为空,Segment-duration为6000,由于timescale为600(该timescale在mvhd中获
得),6000除以timescale刚好为10秒。由此可知我们要延迟播放某个track,可以在elst中插入一个空的entry,Segment-duration设置为需要延迟播放的时间,Media-Time设置为-1,然后在插入一个entry,Segment-duration设置为正常播放时间,Media-Time也就是起始时间设置为0。

5)播放器验证。我们使用vlc播放器打开delay_10s.mp4:
mp4文件elst研究 - 第7张  | Jianchihu
前10秒视频没有播放,而声音正常播放,到第10秒时视频才开始播放,等声音播放结束后,视频还会播放10秒,可以看出视频确实是推迟了10秒播放,此时音视频已经不同步了。

不是所有的播放器都支持elst的,我测试了下,vlc与potplayer支持,windows自带播放器就不支持。

ffmpeg相关代码分析

下面结合ffmpeg中相关代码以及上面的视频延迟10秒的例子分析,看ffmpeg中对elst数据如何处理。ffmpeg中elst entry数据存放在MOVElst 结构体中:

typedef struct MOVElst { int64_t duration; int64_t time; float rate; } MOVElst;

1

2

3

4

5

typedef struct MOVElst {

    int64_t duration;

    int64_t time;

    float rate;

} MOVElst;

duration对应mp4标准中的segment_duration
time对应mp4标准中的media_time

在ffmpeg代码libavformat\mov.c中的mov_build_index函数中有如下代码:

if (sc->elst_count) { int i, edit_start_index = 0, unsupported = 0; int64_t empty_duration = 0; // empty duration of the first edit list entry int64_t start_time = 0; // start time of the media for (i = 0; i < sc->elst_count; i++) { const MOVElst *e = &sc->elst_data[i]; if (i == 0 && e->time == -1) { /* if empty, the first entry is the start time of the stream * relative to the presentation itself */ empty_duration = e->duration; edit_start_index = 1; } else if (i == edit_start_index && e->time >= 0) { start_time = e->time; } else unsupported = 1; } if (unsupported) av_log(mov->fc, AV_LOG_WARNING, "multiple edit list entries, " "a/v desync might occur, patch welcome\n"); /* adjust first dts according to edit list */ if ((empty_duration || start_time) && mov->time_scale > 0) { if (empty_duration) empty_duration = av_rescale(empty_duration, sc->time_scale, mov->time_scale); sc->time_offset = start_time - empty_duration; current_dts = -sc->time_offset; if (sc->ctts_count>0 && sc->stts_count>0 && sc->ctts_data[0].duration / FFMAX(sc->stts_data[0].duration, 1) > 16) { /* more than 16 frames delay, dts are likely wrong this happens with files created by iMovie */ sc->wrong_dts = 1; st->codec->has_b_frames = 1; } } if (!unsupported && st->codec->codec_id == AV_CODEC_ID_AAC && start_time > 0) sc->start_pad = start_time; }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

    if (sc->elst_count) {

        int i, edit_start_index = 0, unsupported = 0;

        int64_t empty_duration = 0; // empty duration of the first edit list entry

        int64_t start_time = 0; // start time of the media

        for (i = 0; i < sc->elst_count; i++) {

            const MOVElst *e = &sc->elst_data[i];

            if (i == 0 && e->time == -1) {

                /* if empty, the first entry is the start time of the stream

                 * relative to the presentation itself */

                empty_duration = e->duration;

                edit_start_index = 1;

            } else if (i == edit_start_index && e->time >= 0) {

                start_time = e->time;

            } else

                unsupported = 1;

        }

        if (unsupported)

            av_log(mov->fc, AV_LOG_WARNING, "multiple edit list entries, "

                   "a/v desync might occur, patch welcome\n");

        /* adjust first dts according to edit list */

        if ((empty_duration || start_time) && mov->time_scale > 0) {

            if (empty_duration)

                empty_duration = av_rescale(empty_duration, sc->time_scale, mov->time_scale);

            sc->time_offset = start_time - empty_duration;

            current_dts = -sc->time_offset;

            if (sc->ctts_count>0 && sc->stts_count>0 &&

                sc->ctts_data[0].duration / FFMAX(sc->stts_data[0].duration, 1) > 16) {

                /* more than 16 frames delay, dts are likely wrong

                   this happens with files created by iMovie */

                sc->wrong_dts = 1;

                st->codec->has_b_frames = 1;

            }

        }

        if (!unsupported && st->codec->codec_id == AV_CODEC_ID_AAC && start_time > 0)

            sc->start_pad = start_time;

    }

第8行代码中可以知道,如果e->time == -1,也就是第一个elst为空,此时得到empty_duration,按前面的例
子该值为10*timescale,下一个for循环得到start_time =e->time,值为0。

在第26行代码中,可知sc->time_offset =0 -10*timescale = -10timescale,然后所有dts都要减去该sc->time_offse,最后结果是都加上10timescale,与原来时间戳比相当于延迟了10s。所以当mp4存在elst时,dts要按如下计算:
1.参考上述ffmpeg代码得到time_offset
2.解码时间戳dts = sample_delta * n – time_offset,其中sample_delta在stts中获得,如果存在B帧,还要从ctts中获得sample_offset,此时:
显示时间戳pts= dts+ sample_offset
否则pts = dts。

接下来我们使用ffmpeg验证下,打印出所有viedeo packet的dts与pts:
mp4文件elst研究 - 第8张  | Jianchihu

可以看到所有时间戳都偏移了230000,也就是10timescale,在video track的mdhd中可知timescale为23000,刚好是10timescale = 23000*10:
mp4文件elst研究 - 第9张  | Jianchihu

由此可知elst的作用就是使某个track时间戳偏移,达到延迟播放的效果。在我们解析mp4文件时,如果存在elst,一定要解析,然后配合stts与ctts,这样才可以得到正确的时间戳。

转自http://blog.jianchihu.net/mp4-elst-box.html。

猜你喜欢

转载自blog.csdn.net/evsqiezi/article/details/81703835