FFmpeg 如何播放带「图片」的 M3U8 视频,IJKPlyaer 适配非标 TS 文件

489 篇文章 14 订阅
464 篇文章 13 订阅

1、前言

如果你经常接触音视频,那么对于 M3U8 应该不会陌生, M3U8 简单来说就是 HLS(HTTP Live Streaming) ,指的是苹果开发的基于 HTTP 协议的流媒体解决方案,它可以在普通的 HTTP 的应用上直接提供点播和直播的能力。

在 HLS 里会将视流文件切分成小片(ts)并建立索引文件(M3U8),一般如下图所示,首先会有一个 M3U8 文件,然后对应在 #EXTINF 的 tag 下会有很多 TS 格式切片,这应该是我们认知中的 M3U8 文件的标准。

详细讲解 M3U8 的这里就不详细展开,感兴趣的可以看之前分享过的探索移动端音视频与GSYVideoPlayer之旅 。

「那么,如果在 M3U8 里的不是 TS 链接,而是 png 链接或者 bmp 链接会是什么情况」?今天的主题就是探讨如何适配带有图片的非标准 M3U8 视频。

「我们不鼓励「非标」,而是通过「非标」的适配来做科普」

2、为什么 M3U8 里会有图片?

首先,标准的 HLS 协议里 M3U8 文件内肯定是 TS 的切片链接,「那么为什么会有 png/bmp 之类的图片链接存在?或者说,为什么会是图片链接」

这就不得不说「劳动人民的智慧」,众所周知,如果想让一个视频加载更快,那么最简单的办法就是给视频上 CDN ,但是碍于某些团队或者个人「囊中羞涩」,所以开始有人瞄上了公共图床的 CDN ,然后再结合 M3U8 的特性,一套民间的「免费」 视频 CDN 潜规则就这样悄然流行起来。

如果你想将一个完整视频伪装成图片上传公共图床明显不现实,因为体积太大,很多图床也会对图片的大小做限制,但是如果是 M3U8 ,那么就可以把「视频分解成无数 TS ,再把 TS 伪装成图片分批上传,这样就可以给视频「依附」上 CDN 的能力」

3、TS & 图片

如下图所示,这就是一个「非标」的 M3U8 视频链接,可以看到 #EXTINF tag 下会的链接都是 png格式的后缀,「那么这种 png 后缀,会不会影响视频播放呢」

「答案是不会,因为 FFmpeg 里播放并不是认定后缀」,而是通过读取每个 #EXTINF tag 链接的二进制 Header,最终匹配它们的封装和编解码格式。

所以其实在 M3U8 里 #EXTINF tag 下的链接后缀并不重要,可以是 png 或者 bmp ,甚至你写 txt 也是可以的,重点其实是包本身的编码。

那么如果这个视频链接,真的是一个图片呢?如下图所示,可以看到这个 png 本身就是一个完整的图片,不过这个图片的大小和它本身的质量并不匹配,毕竟这样一个图片不可能高达 1.9 MB 。

如下图所示,我们查看这张图片的二进制,可以看到文件的 Header 确实是 PNG ,但是后面还有类似 FFmpeg Service 这样的描述,可以确实这就是一个伪装成真实 PNG 格式的视频文件。

从二进制字节看可以发现这就是一个 TS 封装的视频文件,因为在它的二进制代码里,有「以 0x47 开头,长度为 188 字节,并且通过 0xFF 进行填充的规律 packet 存在」

后面我们会详细解释。

「那么这个 PNG 可以正常播放吗?答案是可以的」。那为什么明明是 PNG 的 Header ,却可以被解析成视频?

首先 FFmpeg 在播放前,会根据前面提到的 0x47 / 188 这个特征去识别这是一个 TS 封装的视频,之后在 mpegts.c 的对应封装处理逻辑里,会针对识别 0x47 作为包的起始位置去解析,所以 PNG 包部分会被忽略。

0x47 是一个 TS 包的固定 header ,一般一个 TS 包是 188 字节,不够长度一般会用 0xFF 填充」,而 FFmpeg 会针对每个格式去做识别,计算它们的 score ,根据每种格式的 score 决定它可能是什么格式,比如 mpegts.c 里是 mpegts_probe函数,它通过 analyze 函数就会找到 0x47 起始做一系列的判断。

另外还一个叫 mpegts_read_header的函数会读取数据头信息,比如解析出 TS 流当中的数据包大小,节目信息,PMT表,Video PID,Audio PID 等等,这些也是 TS 流播放的重要依据。

而在 mpegts.c 里最重要的 read_packet函数也是,读取的时候会读取 TS_PACKET_SIZE(188)的大小,然后判断包的首字节是不是 0x47 ,如果不是就通过 mpegts_resync重新同步一下去尝试寻找 0x47

「可能这时候细心的你已经发现了「盲点」,前面 PNG 的 Header 二进制里不就是 89 50 4E 47吗?这里不也是有 0x47 ?,这种情况下 mpegts.c 在解包的时候不就会「错乱」了吗」

如下图所示,因为如果从图片的 0x47 开始算, 以 188 的包长度计算,下一个包不就找不到 0x47 了吗?

「答案是会,但是有方法保证它不会」。这就不得不提 mpegts_resync 函数,在前面截图的代码里有 if ((*data)[0] != 0x47)时会调用 mpegts_resync ,如下代码所示,它的关键作用是:

  • 首先通过 avio_seek 往回移动 -FFMIN(seekback, pos) 的大小,对应到上面的图片,就是把指针移回读取上图黄色标注的数据开始的 FF 位置,也就是还没读取这一块数据的时候。

  • for 循环里通过 avio_r8 让指针往前逐步读字节,当遇到 0x47 就停下来,让指针回到 0x47 ,然后调用 reanalyze 重新分析数据。

  • 可以看到,在经过 mpegts_resync 函数同步之后,指针回重新被同步到上图黄色标准里的 0x47位置,再重新执行 ffio_read_indirect 打开一组 188 的数据,从而让 TS 包解析回归正常

/* XXX: try to find a better synchro over several packets (use
 * get_packet_size() ?) */
static int mpegts_resync(AVFormatContext *s, int seekback, const uint8_t *current_packet)
{
    MpegTSContext *ts = s->priv_data;
    AVIOContext *pb = s->pb;
    int c, i;
    uint64_t pos = avio_tell(pb);
​
    avio_seek(pb, -FFMIN(seekback, pos), SEEK_CUR);
​
    //Special case for files like 01c56b0dc1.ts
    if (current_packet[0] == 0x80 && current_packet[12] == 0x47) {
        avio_seek(pb, 12, SEEK_CUR);
        return 0;
    }
​
    for (i = 0; i < ts->resync_size; i++) {
        c = avio_r8(pb);
        if (avio_feof(pb))
            return AVERROR_EOF;
        if (c == 0x47) {
            avio_seek(pb, -1, SEEK_CUR);
            reanalyze(s->priv_data);
            return 0;
        }
    }
    av_log(s, AV_LOG_ERROR,
           "max resync size reached, could not find sync byte\n");
    /* no sync found */
    return AVERROR_INVALIDDATA;
}

所以上述这个 PNG 图片尽管会有一点「冗余」的错误数据,但是最终还是可以被 mpegts.c 正常解析,从而播放。

「所以 M3U8 里有图片链接,是因为「劳动人民」需要「免费 CDN」,而链接后缀和前置格式不大会影响视 TS 封装的播放,现有的 IJKPlayer 封装的 FFmpeg 就支持播放伪装成图片的 TS 视频链接。」

4、正文

对,这里开始才是正文,前面的 png 操作还算是比较「常规」,但是接下来的一些特殊案例,就是如果你不适配,大概就播放不了的场景。

因为把 TS 伪装成图片是一种「非标准」的做法,自然就存在各式各样的「骚操作」,例如下面这个 M3U8,就包含有 bmp、png、ts 三种格式的链接。

最有趣的事,尽管链接上写的时候 png ,但是实际这个链接的 header 描述里也是一个 bmp ,然后这个 bmp 的数据还是还被 AES-128 加密。

我们下载这个 M3U8 里其中一个 bmp,如下图所示,通过大小可以很明显看到它也是一个伪装成 bmp 的视频链接,但是它有点特殊,因为:「它经过了 M3U8 的 AES-128 加密,同时它的二进制组成也有些特殊」

如下图所示,查看这个加密的 bmp 文件的二进制,可以看到从 Header 看它确实是 bmp 格式,同时因为 TS 视频的数据被 AES-128 加密了,所以此时我们看不到原始的 TS 封装信息,但是因为它所在的 M3U8 里有可用的加密 key,所以我们可以直接通过一些工具来下载和解密。

比如我们可以通过开源的 M3U8-Downloader 来下载得到一个解密后 bmp,如下图所示是上面的 bmp 文件经过下载解密之后的二进制格式,可以看到此时已经可以看到一些我们熟悉的信息,比如 H264 的描述,比如 0x47和大量 0xFF 填充。

另外可以看到,此时的 BMP 因为 「AES-128」的解密作用下,此时的 bmp 已经不是一个正常的图片格式,无法以图片的形式打开查看。

因为 Header 没了。

同时,此时的伪装 TS 封装在解密后依然不是 0x47 开头,所以如下图所示,视频在播放时,会找到我们蓝色选中第二行里的 0x47 的位置,然后开始往后读取一个 188 长度的 TS 包进行解析播放。

「但是问题来了,此时播放出来的视频,会出现没有画面的情况」。为什么会有这种情况?这就要说到前面提到的 mpegts_resync

因为从第一个 0x47 开始读取,那么第二个包就会是上图画出来的红色部分,因为不是 0x47 开头,所以会通过 mpegts_resync 函数找到绿色的 0x47 ,然后继续往后读取。

「这样乍一看没有什么问题,但是其实忽略了黄色部分的 0x47 ,如果仔细去数,你就会发现黄色部分的 0x47 到绿色的 0x47 ,恰好就是 188 的长度,所以其实这部分应该是一个完整的 TS 包,并且是很重要的一个包,也是因为它没被正确读取,所以导致了播放出现没有画面的情况。

那么这个包是什么,为什么它会这么重要?

【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发

【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~

5、TS & PAT & PMT

我们前面会出现画面无法被解析,其实就是因为我们说被「丢失」的包导致的,它恰好就是 TS 里的 PAT 包 :

  • PAT (Program Association Table)主要的作用就是指明了 PMT 表的 PID 值

  • PMT(Program Map Table)主要的作用就是指明了音视频流的 PID 值

  • PID 确定 TS 包中的数据属于什么类型

「所以由于 PAT 没有被正确的解析,所以没有得到正确 PMT,从而没有找到正确的视频编码包的 PID,所以出现了没有画面的情况」

这也是为什么 PAT 包那么重要,简单来说,正常情况下解析一个 TS 封装的流程为:

TS 流里每个 packet 一般都是 188 个字节,解析 TS 需要先解析每个 packet ,然后需要从一个 packet 中解析出 PAT 的 PID,PAT 的 PID 一般为 0,然后从 PAT 包中解析出 PMT 的 PID,再根据 PMT 的 PID 找到 PMT 包,在从 PMT 包中解析出 Video 和 Audio 的PID,然后根据PID找出相应的音视频包。

如下图所示,一般 TS 包的 header 主要由 4 个字节组成,其中 sync_byte 是一个字节(8b),固定为 0x47 ,而 PID 是一个 13b 的二进制,一般 PID 为 0 的 packet 就会被认定为是 PAT。

比如前面被我们忽略的 47 40 00 10 它对应二进制是 0100 0111 0100 0000 0000 0000 0001 0000 ,按照上面拆分:

sync_byte(1B)0x47 / 0100 0111
transport_error_indicator (1b)传输错误指示符,通常都为 0,这里也是 0
payload_unit_start_indicator(1b)负载单元起始标示符,一个完整的数据包开始时标记为1,这里恰好是 1
transport_priority(1b)传输优先级,0为低优先级,1为高优先级,通常取 0,这里恰好是 0
PID(13b)这里恰好就是 0 0000 0000 0000,也就是 0,「PID 为 0 就说明这个 TS 包是 M3U8 里的 PAT 包」
Transport_scrambling_control(2b)传输加扰控制,00表示未加密,这里是 00
Adaptation_field_control(2b)00保留;01 为无自适应域,这里为 01
Continuity_counter(4b)表示该计数器为 0,PID 相同的包的计数因该是连续,递增计数器,从0-f,起始值不一定取 0,但 PID相同的包计数器必须是连续,这里是 0000

所以可以看到,被我们忽略的 47 40 00 10 开头的包,恰好就是最重要的 PAT 包,这也是为什么这个视频播放是会没有画面的原因,因为最终对应视频留的 PID 没有被解析出来。

然后我们再去看这个 PAT 表里的数据,如下图所示是 PAT 的内容部分的结构示意图,我们主要需要的是 Program Number(PMT) 的 PID ,在 N loop 部分前有 64b ,也就是 8 个字节,后面 N loop部分才是开始循环的实际节目表,其中一个节目是 32b ,也就是 4 个字节,最后 CRC 结束标志为 32b ,也就是 4 个字节。

所以回到二进制里,黄色部分就是需要固定字节,然后红色下划线的 00 01 EF FF 就是节目表,「该 PAT 里只有一个节目单,其中 00 01 是 number ,也就是节目 number 为 01 , PID 是 FFF, 也就是该节目的 PID 是 4095」

黄色前还有一个 00 属于 adapter 区的,因为前面 Adaptation_field_control 是 01。

然后我们在看下一个 TS 包,如下图红色部分,它的 Header 是 47 4F FF 01 ,对应的二进制就是 0100 0111 0100 1111 1111 1111 000 0001 ,那么它的 PID 就是 0 1111 1111 1111 这 13 位,也就是 FFF (4095)。

所以,到这里一切都清晰了,「因为忽略的是 PAT 包,所以会导致后面这个 PMT ID 4095 不被解析为特殊的 TS 包,从而获取不到对应的节目数据」

那 PMT 如何读取出流信息?如下图所示是一个 PMT 的 TS 包结构,我们直接看 N loop 部分,一个 loop 大概要 40b ,也就是 5 个字节,其中我们主要是 stream type 和 elementary PID。

其中 stream type 对应的字节代表了流的具体类型,比如 0x0f 就是 aac 音频, 0x1b 就是 h264 的视频,所以 TS 里可以通过 PMT 得到需要当然封装具体的音视频解码格式。

那么回到二进制里,如下图所示,结合 PMT 的结构,可以看到有两个 stream ,其中 stream_type h.264 编码对应 0x1b,aac 编码对应 0x0f ,而 E100 : 111[0 0001 0000 0000] ,后 13 位也就是 256,所以视频的 PID 是 256 ,也就是 h264 的视频 pid 是 256 ,而 acc 的音频的 PID 是 257。

我们再看下一个包,可以看到这个包里有 264 的描述,它的 header 是 47 41 00 31 ,也就是 0100 0111 0100 0001 0000 0000 0011 0001 ,对应的 PID 就是 0 0001 0000 0000 ,也就是 256,这就和前面的 PMT 继续对应上了。

image-20230331160122249

更直观一点,我们简单写一个 python 脚本,输出下所有的 PID ,可以看到除了 0 和 4095 ,剩下的就都是 256 和 257 这样的流数据包,所以到这里就可以完全对应上: 「PID 为 0 的包是 PAT ,通过 PAT 得到 PMT 的 PID ,找到 PID 就可以得到 stream type 和 stream pid ,然后就可以找到对应的 stream pid 的 TS 包去读取音视频流数据」

# 导入需要的模块
import sys
​
# 定义常量
TS_PACKET_SIZE = 188
​
# 打开 TS 文件
with open(sys.argv[1], 'rb') as ts_file:
    pids = []
    # 循环读取 TS 数据包
    while True:
        ts_packet = ts_file.read(TS_PACKET_SIZE)
        if not ts_packet:
            break
​
        # 提取 PID 并输出
        pid = (ts_packet[1] & 0x1F) << 8 | ts_packet[2]
        pids.append(pid) 
    print(pids)

所以前面的的「奇奇怪怪」的编码,恰好会让 FFmpeg 忽略掉 PAT 数据,从而导致加载到节目表而导致没有画面。

6、开始适配

基于这个逻辑,我觉得应该是首先解决 PAT 包被忽略的问题,所以如下代码所示,在 mpegts.cread_packet 里我添加了 if((*data)[0] == 0x47 && (*data)[188] != 0x47) 的判断,如果包是以 0x47 开头,但是下一个包不是 0x47 ,那么就在包内重新去寻找一个能「首尾相接」的 0x47 TS 包,然后重新 ffio_read_indirect

static int read_packet(AVFormatContext *s, uint8_t *buf, int raw_packet_size,
                       const uint8_t **data)
{
    AVIOContext *pb = s->pb;
    int len;
    len = ffio_read_indirect(pb, buf, TS_PACKET_SIZE, data);
    if (len != TS_PACKET_SIZE)
        return len < 0 ? len : AVERROR_EOF
​
​
    for (;;) {
        len = ffio_read_indirect(pb, buf, TS_PACKET_SIZE, data);
        if (len != TS_PACKET_SIZE)
            return len < 0 ? len : AVERROR_EOF;
        /* check packet sync byte */
        if ((*data)[0] != 0x47) {
            /* find a new packet start */
​
            if (mpegts_resync(s, raw_packet_size, *data) < 0)
                return AVERROR(EAGAIN);
            else
                continue;
        } else {
            /  / / 添加的部分  /  /  /
            if((*data)[0] == 0x47 && (*data)[188] != 0x47) {
                for(int i = 0; i < TS_PACKET_SIZE; i++) {
                    if((*data)[i] == 0x47 && (*data)[i+188] == 0x47) {
                        avio_seek(pb, i, SEEK_CUR);
                        avio_seek(pb, -TS_PACKET_SIZE, SEEK_CUR);
                        reanalyze(s->priv_data);
                        len = ffio_read_indirect(pb, buf, TS_PACKET_SIZE, data);
                        if (len != TS_PACKET_SIZE)
                               return len < 0 ? len : AVERROR_EOF;
                        return 0;
                    }
                }
            } else {
                break;
            }
        }
    }
    return 0;
}

重新打包之后,它确实可以解析出画面了,而画面却出现花屏,花屏肯定是播放过程中出现了丢包,导致 IBP 帧解析出现异常,所以我们上面的写法存在问题。

而恰巧这时候我们发现,前面 bmp 填充部分的长度,恰好也是 188 字节,这种巧合让我不禁怀疑,是不是其实我们不需要忽略这个前置字节?

所以我们直接无视 0x47 的开头,直接读取解析(因为后续一些解析逻辑也会判断 0x47 ,所以这里我们强行无视),然后重新打包之后,我们惊喜的发现可以播放了,也不会花屏了,但是又有新的问题出现:一个 TS 播放完了它不会切换到下一个 TS 。

然后我们再去看这个 TS 文件的末尾,原来文件末尾填充了大量的 0x00 字节,从而导致读取时无法正常触发结束标识。

所以我们再次简单修改下,当遇到 0x00 开头的包时,我们用 mpegts_resync 函数处理一下,如果找不到正常的包,我们就可以直接返回 AVERROR(EAGAIN) 结束这个 TS 的播放。

到这里我们可以看到这个 M3U8 可以正常播放了,对应的 stream 也可以被解析出现,虽然这里的修改很简单粗暴,但是这样的修改,就可以在兼容正规协议的情况下,也可以适配到这种「民间非标」支持,重点是通过这个例子,可以形象的普及 TS 封装里的基础概念。

以上修改需要调整 FFmpeg 的源码,然后重新构建动态库。

另外这个格式的文件的 ExoPlayer 下也是无法被播放,主要是因为前面说的读错了 0x47 的包头位置,「因为后面 0xFF 太多,会导致超过两个 TS_PACKET_SIZE 的判断」,从而抛出异常,如果要想 ExoPlayer 也支持播放,可以从这点切入去修改源码。

另外你会发现浏览器是可以播放这类链接,因为如 hls.js 在这方面的检测没有那么严谨。

7、最后

上述播放调整是基于 IJKPlayer 上的 FFmpeg 版本进行,虽然如今 IJKPlayer 已经没有维护,但是基于 IJKPlayer 做一些调整优化还是很方便。

当然,因为 IJKPlayer 整体构建环境比较老,所以如果你重新构建编译,可以参考 GSYVideoPlayer 下的 编译 IJKPlayer so 相关支持 ,目前文档已经支持到 Mac M1/M2 下的环境 。

还有调试这类 TS 文件,个人建议使用本地播放 M3U8 来进行测试,这样我们可以更方便在播放时动态修改本地的 TS 二进制字节,例如可以修改 M3U8 为下面的文件格式。

当然,如果播放本地 M3U8 遇到了下方类似的错误提示,可以参考下方代码添加 "allowed_extensions", "ALL" 到 IJKPlayer 里来临时允许播放。

(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "protocol_whitelist", "crypto,file,http,https,tcp,tls,udp,rtmp,rtsp");
(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "allowed_extensions", "ALL");
(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", 1);

好了,本篇到这里就结束了,通过讲解适配对 TS 封装一系列的骚操作,相信大家对 TS 的一些基础概念都有了一定的认识,最后总结一下;

  • 「对于 TS 解码,视频的后缀格式和封装 header 并不会实际影响播放效果」

  • 「TS 封装是以 0x47 开头,188 字节长度,会用0xFF 做冗余填充的包格式」

  • 「TS 封装里 PID 是唯一标识,而 PID 为 0 的包是 PAT 包」

  • 「PAT 包很重要,因为通过 PAT 包才能找到 PMT 包,找到 PMT 包才能正确获取音视频的 PID」

  • 「FFmpeg 的 mpegts.c 里, mpegts_resync 函数可以帮助你重新同步到 TS packet 的包头」

原文链接:音视频骚操作,FFmpeg 如何播放带「图片」的 M3U8 视频,IJKPlyaer 适配非标 TS 文件

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ffmpeg是一款功能强大的多媒体处理工具,它支持各种音视频格式的转换和处理。要实现自动下载m3u8视频并合并为ts格式,可以使用ffmpeg的命令行工具和一些脚本编程。 首先,需要编写一个脚本或程序来自动从指定的m3u8链接下载视频文件。可以使用编程语言(如Python)来实现这个脚本,使用HTTP请求库来获取m3u8文件的内容,并解析出各个ts分片的链接。 接下来,可以使用ffmpeg的命令行工具,利用解析到的ts分片链接来下载每个分片,并同时将它们合并为一个完整的ts视频文件。以下是示例的ffmpeg命令: ffmpeg -i "url_to_ts1" -c copy -bsf:a aac_adtstoasc output.ts 上述命令中,"url_to_ts1"表示第一个ts分片的链接,可以根据实际情况替换为对应的分片链接。"-c copy"表示复制原始编码格式,"-bsf:a aac_adtstoasc"用于修复aac音频流中的格式问题。"output.ts"表示最终生成的合并后的ts文件。 要实现自动下载并合并多个ts分片,可以在脚本中使用循环来依次下载每个分片,并调用ffmpeg命令行工具来合并这些分片。 需要注意的是,m3u8文件通常是分段加载的,因此可能需要等待所有分片下载完成才能合并为完整的视频文件。可以在脚本中增加相应的等待时间或轮询机制,确保所有分片都已下载完成后再调用ffmpeg进行合并操作。 总之,通过编写脚本或程序,结合ffmpeg命令行工具,可以实现自动下载m3u8视频并合并为ts格式的功能。 ### 回答2: ffmpeg是一个开源的音视频处理工具,它可以用来处理各种格式的音视频文件m3u8是一种常见的音视频流媒体播放列表格式,通常用于将一个音视频文件分割成多个小的ts文件,并通过HTTP协议进行传输和播放。 要使用ffmpeg自动下载m3u8并合并为ts文件,可以按照以下步骤进行操作: 1. 下载ffmpeg并安装到本地计算机上。 2. 在命令行中输入以下命令来下载m3u8文件: ```shell ffmpeg -i URL_OF_M3U8_FILE -c copy OUTPUT_FILENAME.ts ``` 其中,`URL_OF_M3U8_FILE`是m3u8文件的URL地址,`OUTPUT_FILENAME`是输出的ts文件名。 命令中的`-c copy`选项表示直接复制视频和音频流而不进行重新编码,以提高处理速度。 3. 执行命令后,ffmpeg将会自动下载m3u8文件,并将其合并为一个ts文件。合并过程可能需要一些时间,取决于m3u8文件的大小和网络状况。 4. 执行完成后,即可在当前目录下找到合并后的ts文件。 需要注意的是,以上命令仅适用于能够公开访问的m3u8文件。若m3u8文件需要身份验证或具有访问控制,请先确保具备合法的使用权限。 总之,使用ffmpeg自动下载m3u8并合并为ts文件,是一种便捷的处理方式,适用于需要分析或修改m3u8文件的情况。 ### 回答3: FFmpeg是一种强大的开源多媒体处理工具,它可以用于从网络上自动下载M3U8视频流,并将其合并为TS格式。M3U8是一种常用的流媒体播放列表文件格式,常用于存储视频的分段URL链接。 要使用FFmpeg自动下载并合并M3U8TS,我们可以使用以下命令行操作: 1. 首先,我们需要安装FFmpeg工具,并将其添加到系统路径中。 2. 然后,在命令行中输入以下命令: ``` ffmpeg -i [M3U8链接] -c copy -bsf:a aac_adtstoasc output.ts ``` 在此命令中,我们需要将[M3U8链接]替换为实际的M3U8文件的URL链接。该命令使用`-i`参数指定输入M3U8文件,`-c copy`参数用于复制视频和音频流,以保持原始质量,`-bsf:a aac_adtstoasc`参数用于处理AAC音频流的编码问题,`output.ts`为输出的合并后的TS文件名称。 3. 输入命令后,FFmpeg将自动开始下载M3U8文件并合并为TS格式。下载进度将在命令行中显示。 4. 下载完成后,我们将在当前目录中找到生成的TS文件,这是M3U8下载和合并的结果。 总结起来,FFmpeg利用其强大的功能,可以方便地自动下载并合并M3U8视频流为TS格式。这在处理流媒体和视频下载方面非常有用,为用户提供了一种方便快捷的方法来获取他们所需的视频内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值