I'm trying to convert video files into HLS streams on a AWS Lambda. The ffmpeg configuration I have setup works for normal (Non HLS) transcoding, but in case of HLS it throws the following error:
stderr:
frame= 341 fps= 84 q=34.0 q=32.0 q=28.0 size=N/A time=00:00:12.52 bitrate=N/A speed= 3.1x
frame= 385 fps= 85 q=34.0 q=31.0 q=27.0 size=N/A time=00:00:13.97 bitrate=N/A speed=3.08x
frame= 433 fps= 86 q=33.0 q=30.0 q=27.0 size=N/A time=00:00:15.55 bitrate=N/A speed=3.08x
[hls @ 0x702c480] Cannot use rename on non file protocol, this may lead to races and temporary partial files
[hls @ 0x702c480] Opening '/tmp/stream_0.m3u8' for writing
[hls @ 0x702c480] Opening '/tmp/stream_1.m3u8' for writing
[hls @ 0x702c480] Opening '/tmp/stream_2.m3u8' for writing
[hls @ 0x702c480] Opening '/tmp/master.m3u8' for writing
ffmpeg was killed with signal SIGSEGV
And I error thrown by fluent-ffmpeg is this:
2023-03-06T10:21:44.555Z 15a0a43b-5e24-42ac-ae72-fe04f0c72a3e ERROR Invoke Error
{
"errorType": "Error",
"errorMessage": "ffmpeg was killed with signal SIGSEGV",
"stack": [
"Error: ffmpeg was killed with signal SIGSEGV",
" at ChildProcess.<anonymous> (/var/task/node_modules/fluent-ffmpeg/lib/processor.js:180:22)",
" at ChildProcess.emit (node:events:513:28)",
" at ChildProcess._handle.onexit (node:internal/child_process:291:12)"
]
}
This is the code I run on AWS Lambda to convert video files into HLS:
export const compressToHLS = (sourcePath, outputFolder) =>
new Promise((resolve, reject) => {
Ffmpeg(sourcePath)
.complexFilter([
{
filter: "split",
options: "3",
inputs: "v:0",
outputs: ["v1", "v2", "v3"],
},
{
filter: "scale",
options: {
w: 1280,
h: 720,
},
inputs: "v1",
outputs: "v1out",
},
{
filter: "scale",
options: {
w: 960,
h: 540,
},
inputs: "v2",
outputs: "v2out",
},
{
filter: "scale",
options: {
w: 640,
h: 360,
},
inputs: "v3",
outputs: "v3out",
},
])
.outputOptions([
"-map [v1out]",
"-c:v:0",
"libx264",
"-b:v 3000000",
"-map [v2out]",
"-c:v:1",
"libx264",
"-b:v 2000000",
"-map [v3out]",
"-c:v:2",
"libx264",
"-b:v 1000000",
])
.outputOptions([
"-map a:0",
"-c:a:0 aac",
"-b:a:0 96000",
"-ar 48000",
"-ac 2",
"-map a:0",
"-c:a:1 aac",
"-b:a:1 96000",
"-ar 48000",
"-ac 2",
"-map a:0",
"-c:a:2 aac",
"-b:a:2 96000",
"-ar 48000",
"-ac 2",
])
.outputOptions([
"-f hls",
"-hls_time 10",
"-hls_playlist_type vod",
"-hls_flags independent_segments",
"-hls_segment_type mpegts",
`-hls_segment_filename ${outputFolder}/%v_%d.ts`,
"-master_pl_name master.m3u8",
])
.outputOption("-var_stream_map", "v:0,a:0 v:1,a:1 v:2,a:2")
.outputOption("-preset veryfast")
.output(`${outputFolder}/stream_%v.m3u8`)
.on("start", (cmdline) => console.log(cmdline))
.on("progress", (progress) => {
let prog = Math.floor(progress.percent * 10) / 10;
if (Math.round(prog) % 10 == 0) {
console.log(`${prog}% complete`);
}
})
.on("error", (err, stdout, stderr) => {
if (err) {
console.log(err.message);
console.log("stdout:\n" + stdout);
console.log("stderr:\n" + stderr);
reject(err);
}
})
.on("end", () => resolve())
.run();
});
In this code the sourcePath is usually a presigned URL from S3 (But I have also tried to download a file on /tmp and setting sourcePath as the path to the downloaded file) and outputFolder is tmpdir()
which is /tmp
.
I have the lambda settings configured to have 10GB of memory and 10GB of ephemeral storage (the maximum allowed).
Almost a week ago, I encountered the same problem and solved my problem with this method
ffmpeg()
.input(inputFile)
.complexFilter(`[v:0]split=${split === 0 ? 1 : split + 1}${repeatInput(split)}${repeatResolution(split)}`)
.addOptions([
...mapResolutions(split),
'-hls_playlist_type event',
`-hls_key_info_file ${keyInfoPath}${randKeyName}.keyinfo`,
`-hls_base_url pathFolder/`,
'-master_pl_name master.m3u8'
])
.addOptions(`-var_stream_map`, repeatStreamMap(split))
.output(`${hlsPath}file_%v.m3u8`)
.run()
function repeatInput(num) {
let str = '';
for (let i = 0; i <= num; i++)
str += `[in${i}]`;
return `${str};`;
}
/**
* Repeat resolution
* @param {Number} num
* @return {string}
*/
function repeatResolution(num) {
const obj = [
{w: 480, h: 360},
{w: 854, h: 480},
{w: 1280, h: 720},
{w: 1920, h: 1080}
];
let str = '';
for (let i = 0; i <= num; i++)
str += `[in${i}]scale=w=${obj[i].w}:h=${obj[i].h}[out${i}]${num === i ? '' : ';'}`;
return str;
}
/**
* Map resolutions
* @param {Number} num
* @return {String[]}
*/
function mapResolutions(num) {
const arr = [];
const bitrateArr = [
'1000k',
'2000k',
'4000k',
'6000k'
];
for (let i = 0; i <= num; i++)
arr.push(`-map [out${i}]`, `-c:v:${i} libx264`, `-b:v:${i} ${bitrateArr[i]}`);
for (let i = 0; i <= num; i++)
arr.push('-map a:0');
return arr;
}
/**
* Repeat stream map
* @param {Number} num
* @return {string}
*/
function repeatStreamMap(num) {
let arr = [];
for (let i = 0; i <= num; i++)
arr.push(`v:${i},a:${i}`);
return arr.join(' ');
}
Using the code, I was able to get the desired output through the ffmpeg tool and it works correctly, even I was able to process 4 files of 1 GB at the same time without any error.
Note: The value of split in this pseudo-code helps to output only the resolutions that the video supports. Its value varies from zero to three. By default, ffmpeg is used for all inputs that are written in the option section, output with the corresponding resolutions. If you want to output only the resolutions that the video supports, you need to first get the video resolution type using ffprobe and then create an object with these types.
const hlsResolutionsSupported = [
640, // 360
854, // 480
1280, // 720
1920 // 1080
];
const resolutions = metadata.streams[0].width;
let split = hlsResolutionsSupported.indexOf(resolutions);
split = split === -1 ? 0 : split;
I hope it was useful