关于前端JS将m3u8流合成MP4(h265格式)的记录

最近遇到的问题是将M3u8合成mp4,并下载下来。

初步定的方案是利用mux.js 获取m3u8的ts分片列表

然后通过分别下载ts成二进制流再合成,期间用到了。demuxer这个库,

但是实际测试下来,不是时间有问题就是有声音没有图像。

后来在网上找到了一个js库,m3u8-downloader大家可以去看看,其实也是用到了muxjs的方式,

按照这个库的方式写下来,发现了一个问题,就是他的示例下载没有问题,但是他的示例的ts分片数据是h264的,我们自己系统的ts分片是h265的。所以合成失败了

网上找了无数资源都没找到前端合成的方式。

后来在同事的共同努力下,终于找到了一个库。ffmpeg.wasm这个库。

https://github.com/ffmpegwasm/ffmpeg.wasmicon-default.png?t=N7T8https://github.com/ffmpegwasm/ffmpeg.wasm

具体适用方法呢?里面有案例我就不详细讲了

给那些没有梯子的人大致的讲一下

整体代码:

<template>
  <video :src="video" controls />
  <br />
  <button @click="transcode">Start</button>
  <p>{{ message }}</p>
</template>

<script lang="ts">
import { FFmpeg } from '@ffmpeg/ffmpeg'
import type { LogEvent } from '@ffmpeg/ffmpeg/dist/esm/types'
import { fetchFile, toBlobURL } from '@ffmpeg/util'
import { defineComponent, ref } from 'vue'

const baseURL = 'https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/esm'
const videoURL = 'https://raw.githubusercontent.com/ffmpegwasm/testdata/master/video-15s.avi'

export default defineComponent({
  name: 'App',
  setup() {
    const ffmpeg = new FFmpeg()
    const message = ref('Click Start to Transcode')
    let video = ref('')

    async function transcode() {
      message.value = 'Loading ffmpeg-core.js'
      ffmpeg.on('log', ({ message: msg }: LogEvent) => {
        message.value = msg
      })
      await ffmpeg.load({
        coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
        wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
        workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript')
      })
      message.value = 'Start transcoding'
      await ffmpeg.writeFile('test.avi', await fetchFile(videoURL))
      await ffmpeg.exec(['-i', 'test.avi', 'test.mp4'])
      message.value = 'Complete transcoding'
      const data = await ffmpeg.readFile('test.mp4')
      video.value = URL.createObjectURL(new Blob([(data as Uint8Array).buffer], { type: 'video/mp4' }))
    }
    return {
      video,
      message,
      transcode
    }
  }
})
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

安装方式:

npm install @ffmpeg/ffmpeg @ffmpeg/util

or

yarn add @ffmpeg/ffmpeg @ffmpeg/util

or 

pnpm install @ffmpeg/ffmpeg @ffmpeg/util

我使用的版本是@ffmpeg/ffmpeg 0.12.10 @ffmpeg/util 0.12.1

具体示例代码在这里https://github.com/ffmpegwasm/ffmpeg.wasm/tree/main/apps/vue-vite-appicon-default.png?t=N7T8https://github.com/ffmpegwasm/ffmpeg.wasm/tree/main/apps/vue-vite-app

唯一要说的就是,如果要转m3u8,首先下载m3u8下的ts列表,完成以后放到一个数组里面,

再循环

// 以三个ts片段为例videoURL1 videoURL2 videoURL3 为上一步m3u8获取的ts分片地址
await ffmpeg.writeFile('test1.ts', await fetchFile(videoURL1))
await ffmpeg.writeFile('test2.ts', await fetchFile(videoURL2))
await ffmpeg.writeFile('test3.ts', await fetchFile(videoURL3))

// 这是默认的
await ffmpeg.exec(['-i', 'test.ts', 'test.mp4']) 

// 这是修改后的 这里是重点命令!!!!!!!!!
await ffmpeg.exec(['-i', 'concat:test1.ts|test2.ts|test3.ts', 'test.mp4']) 

还有一个踩坑点,使用它的代码的时候一定会报某个错ShareArrayBuffer is not defined(这玩意而是跨域隔离策略导致的)


const baseURL = 'https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/esm'
await ffmpeg.load({
    coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
    wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
    workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript')
})

首先把这三个下载下来,放到vue3的public文件里面,本地访问。

// 部署以后得Nginx配置


        location / {
            absolute_redirect on;
            port_in_redirect on;
            #autoindex on;
            root /opt/local/web/dist;
            index  index.html index.htm;
            add_header Cross-Origin-Embedder-Policy require-corp;
            add_header Cross-Origin-Opener-Policy same-origin;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host $host;
        }

  add_header Cross-Origin-Embedder-Policy require-corp;
  add_header Cross-Origin-Opener-Policy same-origin;

这两行是必须的,也就是说必须打开同源策略,可能会导致引用其他源的资源出错,比如高德地图不显示logo,不显示定位图层等等,这个可能要单独处理了。

这只是前端合成m3u8的一种方法,如果允许还是让后端做转换把

其他的坑我就不填了,比如跨域。AES加密解密之类的,自己解决一下了

最后贴一段完整下载代码,仅供参考!(baseURL请换成自己的地址,测试的时候可以先用固定地址),这段下载代码,是做了分块处理,每100ts个合成一个视频

const downloadMp4 = async (m3u8Url, filename = `${+new Date()}`, callback) => {
  callback && callback({
    message: `正在请求视频数据源`,
    length: 0,
    current: 0,
    done: false
  })
  const response = await fetch(m3u8Url).then(response => response.text())
  const tsUrlList = [];
  response.split('\n').forEach(item => {
    // if (/.(png|image|ts|jpg|mp4|jpeg)/.test(item)) {
    // 放开片段后缀限制,下载非 # 开头的链接片段
    if (/^[^#]/.test(item)) {
      tsUrlList.push(item)
    }
  })
  callback && callback({
    message: `视频数据源请求完成`,
    length: tsUrlList.length,
    current: 0,
    done: false
  });
  async function transcode() {
    // 将 tsUrlList 分割成多个子数组
    const chunkSize = 100;
    const chunks = [];
    let downloadNumber = 0;
    for (let i = 0; i < tsUrlList.length; i += chunkSize) {
      chunks.push(tsUrlList.slice(i, i + chunkSize));
    }
    // 对每个子数组进行处理
    for (let i = 0; i < chunks.length; i++) {
        const ffmpeg = new FFmpeg()
        ffmpeg.on('log', ({ message: msg }) => {
          console.log(msg)
        })
        const baseURL = '/main';
        callback && callback({
          message: `正在加载合成依赖`,
          length: tsUrlList.length,
          current: downloadNumber,
          done: false
        })
        await ffmpeg.load({
          coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
          wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
          workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript')
        })
        // await ffmpeg.writeFile('test.ts', await fetchFile('/main/123.ts'))
        // await ffmpeg.exec(['-i', 'test.ts', 'test.mp4'])
        callback && callback({
          message: `合成依赖加载完成`,
          length: tsUrlList.length,
          current: downloadNumber,
          done: false
        })
        const chunk = chunks[i];
        let execStr = `concat:`;

        for (let j = 0; j < chunk.length; j++) {
            const element = chunk[j];
            let response;
            downloadNumber ++;
            try {
              response = await fetchFile(element);
            } catch (error) {
              console.error('下载失败:', error);
              continue;  // 如果下载失败,跳过当前循环,进入下一个循环
            }
            await ffmpeg.writeFile(`test${i * chunkSize + j}.ts`, response)
            execStr += `test${i * chunkSize + j}.ts|`;
            callback && callback({
                message: `正在下载`,
                length: tsUrlList.length,
                current: downloadNumber,
                done: false
            })
        }

        callback && callback({
            message: `视频合成中`,
            length: tsUrlList.length,
            current: downloadNumber,
            done: false
        })

        console.log('execStrexecStrexecStrexecStr=>', execStr.substring(0, execStr.length-1))
        await ffmpeg.exec(['-i', execStr.substring(0, execStr.length-1), '-c','copy',`test${i}.mp4`])
        
        // 删除已经处理过的TS文件
        // for (let j = 0; j < chunk.length; j++) {
        //   const filename = `test${i * chunkSize + j}.ts`;
        //   ffmpeg.deleteFile(filename);
        // }

        let data = await ffmpeg.readFile(`test${i}.mp4`)
        let src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }))
        callback && callback({
            message: `视频合成完成,启动下载`,
            length: tsUrlList.length,
            current: i === chunks.length - 1 ? tsUrlList.length : downloadNumber,
            done: i === chunks.length - 1 ? true : false,
        })
        let link = document.createElement('a');
        link.href = src;
        link.download = `${filename}_${i}.mp4`; // 这里设置你想要的文件名
        link.click(); // 这将开始下载
        link.remove(); // 移除 'a' 元素
        ffmpeg.terminate();
    }

  }
  transcode();
}

使用方式:

downloadMp4(hasNeedPlaying.url, filename, (res) => {
    // 这里是下载过程中的回调函数
    message.value[index] = res;
    res.done && hasDone++;
    if (sum === hasDone) {
        // 当前视频下载完成,开始下载下一个
        downloadOneByOne(index + 1);
    }
});

视频

QQ202467-101730

### 回答1: 使用FFmpeg将M3U8格式转换为MP4的步骤如下: 1. 下载FFmpeg并安装。 2. 打开终端或命令提示符,进入M3U8文件所在的目录。 3. 运行以下命令:ffmpeg -i input.m3u8 -c copy output.mp4 其中,input.m3u8是要转换的M3U8文件名,output.mp4是转换后的MP4文件名。 4. 等待转换完成,转换时间根据文件大小和计算机性能而定。 5. 转换完成后,可以在同一目录下找到转换后的MP4文件。 注意:如果M3U8文件中包含多个分段文件,转换过程可能需要一些时间。 ### 回答2: FFmpeg是一个强大的自由软件,可以处理多种音视频文件格式M3U8媒体协议之一,通常用于将一个视频切割成多个小块并且进行分段传输,以便于适应不同网络环境中的传输需求。而MP4则是一种常见的视频文件格式。 如果要将M3U8格式视频文件转换成MP4格式,可以使用FFmpeg来实现。以下是基本的步骤: 1. 下载并安装FFmpeg 首先需要在计算机上安装FFmpeg。在Linux系统中,可以使用命令行sudo apt-get install ffmpeg进行安装;在Windows系统中,请先下载 FFmpeg二进制文件包,然后将其解压缩到一个方便存取的文件夹中。 2. 下载M3U8文件 要将M3U8文件转换为MP4文件,需要先下载M3U8文件。用浏览器打开M3U8文件链接(通常以.m3u8结尾),然后将其中的URL复制到文本编辑器中,保存为.m3u8格式的文件。 3. 使用FFmpeg进行转换 在Windows中,在bin文件夹中打开命令提示符(Shift+右键),然后输入以下命令: ffmpeg.exe -i input.m3u8 -c copy output.mp4 其中,input.m3u8是输入的M3U8文件名,output.mp4是输出的MP4文件名。-c copy选项表示直接将音视频从输入文件复制到输出文件中,以加快转换速度。执行完该命令后,FFmpeg将开始转换。 注意要更改指令中输入输出的文件名,以符合文件实际情况。 4. 等待转换完成 转换时间取决于转换文件的大小和处理器的速度。等转换完成,即可在指定的输出文件夹中找到转换后的MP4视频。 总之,FFmpeg可以轻松地将M3U8格式媒体文件转换成MP4格式,而且转换过程非常简便。 ### 回答3: FFmpeg是一个开源的音视频编码解码库,可以用来实现音视频的录制、转码和播放等功能。而m3u8格式是一种基于HTTP协议的媒体格式,可用于视频直播和点播等场景。在一些情况下,我们可能需要将m3u8格式的视频转换为mp4格式的视频,以便于后续的处理或播放。 FFmpeg提供了丰富的命令行参数,可以实现各种转码效果。在将m3u8格式转换为mp4格式时,可以使用如下命令: ffmpeg -i input.m3u8 -c copy output.mp4 其中,-i参数指定输入文件为input.m3u8;-c copy参数指定使用原始编解码器进行复制,以保持视频和音频的原始质量;output.mp4则是输出文件的文件名。 需要注意的是,由于m3u8格式的视频可能由多个片段组成,因此在进行转换时需要先将所有片段合并为一个文件。我们可以使用FFmpeg中的concat协议来实现该功能。具体操作步骤如下: 1. 创建一个名为filelist.txt的文件,其中列出了所有的片段,格式如下: file 'segment1.ts' file 'segment2.ts' file 'segment3.ts' ... 2. 运行以下命令将所有片段合并为一个文件: ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.ts 其中,-f concat参数指定使用concat协议;-safe 0参数指定禁止检测文件名,因为filelist.txt中的文件名可能无法通过安全检测;output.ts为输出文件名。 3. 最后再运行以下命令将合并后的ts文件转换为mp4文件: ffmpeg -i output.ts -c copy output.mp4 需要注意的是,在进行转换时可能会遇到一些编码格式不支持的问题。此时可以使用FFmpeg中的音视频过滤器来进行转码,以保证输出文件的质量和兼容性。具体的过滤器使用方法可以参考FFmpeg官方文档或相关教程。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值