Làm sao để giảm kích thước file video tăng tốc độ stream và giảm chi phí data transfer. Tôi tìm ra giải pháp sử dụng dịch vụ amazon elastic transcoder hoặc aws elemental mediaconvert. Tôi chọn aws elemental mediaconvert, vì nó rẻ hơn so với transcoder và bao gồm các tính năng bổ sung với bảo mật cao [DRM]. Trong aws elemental mediaconvert, tôi có thể stream video theo cấu hình mà tôi thiết lập. Job convert có thể tự động tạo mỗi khi có file video được upload vào S3 bucket. Tôi cũng có thể nhận thông báo cho trạng thái hoàn thành và lỗi của video convert. Về chi phí, đây là một lựa chọn rất rẻ để chuyển mã file sang các định dạng khác.
AWS Elemental MediaConvert là một dịch vụ chuyển mã video dựa trên tệp tin với các tính năng cấp độ phát sóng. Nó cho phép bạn dễ dàng tạo nội dung video theo yêu cầu (VOD) để phát sóng và phân phối đa màn hình theo quy mô lớn. Dịch vụ kết hợp các khả năng video và âm thanh nâng cao với giao diện dịch vụ web đơn giản và giá cước trả theo mức sử dụng.
Với AWS Elemental MediaConvert, bạn có thể tập trung vào việc cung cấp trải nghiệm phương tiện hấp dẫn mà không phải lo lắng về sự phức tạp của việc xây dựng và vận hành cơ sở hạ tầng xử lý video của riêng mình. Để tìm hiểu thêm, hãy đọc tài liệu về AWS Elemental MediaConvert.
Trong bài viết này, tôi sẽ chia sẻ cách chuyển đổi video sang định dạng MP4 bằng MediaConvert và Lambda. Đồng thời nhận thông báo qua email về trạng thái hoàn thành hoặc lỗi. Framework mà tôi sử dụng để develop là Aws Amplify.
Architecture Overview
Diagram:
Mô tả quá trình:
- Admin upload file video vào s3 folder /video/
- Event Bridge lắng nghe khi có file mới được upload vào folder /video, event trigger và gửi thông tin video vào lambda fuction
- Lambda function nhận được thông tin và tiến hành khởi tạo setting convert video, sau đó khởi tạo Job MediaConvert, ở đây có sử dụng thêm lambda layer FFProbe để lấy được kích thước file video
- MediaConvert nhận được Job và tiến hành process video, sau khi process thành công sẽ upload vào s3 folder /video-convert/
- Event Bridge lắng nghe sự kiện Complated hay Error để gửi mail thông báo cho user thông qua dịch vụ AWS SNS
- User có thể xem file video trong s3 folder /video-convert/ thông qua cloudfront
Để tiếp tục thực hành bài viết hãy đảm bảo rằng bạn đã cài đặt Amplify theo link: https://docs.amplify.aws/cli/start/install/
Bắt đầu develop:
Run command: amplify init
Nhập name, env, AWS Profile còn lại có thể để mặc định
Run command: amplify add function
Khởi tạo: Lambda function (serverless function)
Điền function name: videoConvert
Ngôn ngữ lập trình: nodeJS
> Chọn HelloWorld
Do you want to configure advanced settings > No
Để thực thi mediaconvert thì cần có 2 url:
- Url MediaConvert endpoint
- Default queue
Lấy url MediaConvert:
Vào AWS Console > Chọn region > MediaConvert > Chọn menu > Account
Copy url endpoint: https://xxxxxxxx.mediaconvert.ap-southeast-1.amazonaws.com
Lấy url default queue:
Vào AWS Console > Chọn region > MediaConvert > Chọn menu > Queues > bấm vào Default
Copy ARN:
arn:aws:mediaconvert:ap-southeast-1:573580132305:queues/Default
Tạo S3 Bucket để lưu trữ video
Vào AWS Console > Chọn region > S3 > Create bucket > Bucket name:
onetech-demo-mediaconvert
Update permission S3 để cho phép mediaconvert write file:
Bucket > onetech-demo-mediaconvert > Permissions > Bock public access> Edit > Uncheck Block all public access
Bucket > onetech-demo-mediaconvert > Permissions > Object Ownership > Edit
Config cho phép trigger event bridge vào s3 bucket
Click vào bucket: onetech-demo-mediaconvert
Chọn Properties > Event notifications > Amazon EventBridge > Edit > Turn ON > Save Changes
Start coding
Cài thư viện node JS:
"devDependencies": {
"aws-sdk": "^2.1416.0",
"fluent-ffmpeg": "^2.1.2",
"fs": "^0.0.1-security",
"path": "^0.12.7"
}
cd amplify/backend/function/videoConvert/src
npm install -D aws-sdk fluent-ffmpeg fs path
Develop node_modules > lưu ý thư viên này chỉ để chạy mock ở local nếu up lên server sẽ bị xoá
Sau khi cài xong quay về folder root mở project bằng Visual studio code
Tạo file mới: config/resize.json
Với data:
{
"Queue": "xxxxxx",
"UserMetadata": {
"input": "s3://xxxxxx/videos/Video400mb.mp4",
"environment": "dev"
},
"Role": "xxxxxxxx",
"Settings": {
"TimecodeConfig": {
"Source": "ZEROBASED"
},
"OutputGroups": [
{
"CustomName": "MP4",
"Name": "File Group",
"Outputs": [
{
"ContainerSettings": {
"Container": "MP4",
"Mp4Settings": {}
},
"VideoDescription": {
"Width": 3840,
"Height": 2160,
"CodecSettings": {
"Codec": "H_264",
"H264Settings": {
"GopClosedCadence": 1,
"GopSize": 90,
"GopBReference": "DISABLED",
"MaxBitrate": 3000000,
"SpatialAdaptiveQuantization": "ENABLED",
"TemporalAdaptiveQuantization": "ENABLED",
"FlickerAdaptiveQuantization": "DISABLED",
"RateControlMode": "QVBR",
"QvbrSettings": {
"QvbrQualityLevel": 7
},
"CodecProfile": "MAIN",
"MinIInterval": 0,
"AdaptiveQuantization": "HIGH",
"SceneChangeDetect": "DISABLED",
"QualityTuningLevel": "SINGLE_PASS",
"GopSizeUnits": "FRAMES",
"NumberBFramesBetweenReferenceFrames": 2
}
}
},
"AudioDescriptions": [
{
"CodecSettings": {
"Codec": "AAC",
"AacSettings": {
"Bitrate": 96000,
"CodingMode": "CODING_MODE_2_0",
"SampleRate": 48000
}
}
}
],
"NameModifier": "Resize"
}
],
"OutputGroupSettings": {
"Type": "FILE_GROUP_SETTINGS",
"FileGroupSettings": {
"Destination": "s3://xxxxxx/convert-video/",
"DestinationSettings": {
"S3Settings": {
"AccessControl": {
"CannedAcl": "PUBLIC_READ"
}
}
}
}
}
}
],
"Inputs": [
{
"AudioSelectors": {
"Audio Selector 1": {
"DefaultSelection": "DEFAULT"
}
},
"VideoSelector": {
"Rotate": "AUTO"
},
"TimecodeSource": "ZEROBASED",
"FileInput": "s3://xxxxxx/videos/IMG_0961.MOV"
}
]
},
"AccelerationSettings": {
"Mode": "DISABLED"
},
"StatusUpdateInterval": "SECONDS_60",
"Priority": 0,
"Tags": {
"MediaConvert": "DemoMediaConvert"
}
}
Mở file index.js để coding và tuỳ chỉnh lại file video config:
Ở ví dụ này chúng ta sẽ hard code cố định chiều kích thước video là full HD:
1920×1080 or 1080×1920, các bạn có thể làm tự động dự vào yêu cầu dự án.
Lưu ý những chỗ tô màu vàng, cần phải nhập những giá trị đã tạo bên trên.
const AWS = require('aws-sdk');
AWS.config.update({
region: process.env.REGION,
});
const S3 = new AWS.S3({
signatureVersion: 'v4'
});
//Chỗ này cần update lại endpoint mediaconvert
const MediaConvert = new AWS.MediaConvert({
apiVersion: '2017-08-29',
endpoint: 'https://xxxxx.mediaconvert.ap-southeast-1.amazonaws.com',
});
//Read file from local
const fs = require('fs');
//Get info video
const ffmpeg = require('fluent-ffmpeg');
exports.handler = async (event) => {
try {
if(!event.detail) {
return false;
}
//Chỗ này cần update lại s3 bucket name
const bucket = 'onetech-demo-mediaconvert';
let convertWidth = 1920;
let convertHeight = 1080;
const objectKey = event.detail.object.key;
const regex = /\/[^/]*$/;
let convertFolder = objectKey.replace(regex, '/');
convertFolder = convertFolder.replace('video/', 'convert-video/');
console.log('convertFolder:::', convertFolder);
console.log('objectKey:::', objectKey);
//Check video vertical or horizontal
try {
const s3Params = {
Bucket: bucket,
Key: objectKey
};
const s3Stream = S3.getObject(s3Params).createReadStream();
const info = await new Promise((resolve, reject) => {
ffmpeg(s3Stream).ffprobe((err, info) => {
if (err) {
console.error(err);
reject(err);
return;
}
resolve(info);
});
});
console.log('Video Information:', info);
for (const stream of info.streams) {
if (!stream.width) {
continue;
}
console.log('Video INFO Steam', stream);
if(+stream.width < +stream.height) {
convertWidth = 1080;
convertHeight = 1920;
}
if(stream.rotation && stream.rotation === '-90') {
convertWidth = 1080;
convertHeight = 1920;
}
break;
}
} catch (error) {
console.error(error);
}
console.log('convertWidth', convertWidth);
console.log('convertHeight', convertHeight);
//End check video vertical or horizontal
const jsonData = fs.readFileSync('./config/resize.json', 'utf8');
let convertData = JSON.parse(jsonData);
//Update queue
convertData.Queue = 'arn:aws:mediaconvert:ap-southeast-1:111111111111:queues/Default';
//Update role
convertData.Role = process.env.MEDIA_CONVERT_ROLE;
//Update input file
const inputSource = `s3://${bucket}/${objectKey}`;
const outSource = `s3://${bucket}/${convertFolder}`;
convertData.Settings.Inputs[0].FileInput = inputSource;
//Update output folder
convertData.Settings.OutputGroups[0].OutputGroupSettings.FileGroupSettings.Destination = outSource;
//Update video width and height
convertData.Settings.OutputGroups[0].Outputs[0].VideoDescription.Width = convertWidth;
convertData.Settings.OutputGroups[0].Outputs[0].VideoDescription.Height = convertHeight;
//Update UserMetadata
convertData.UserMetadata.input = objectKey;
convertData.UserMetadata.environment = process.env.ENV;
convertData.UserMetadata.output = convertFolder;
convertData.UserMetadata.s3_bucket = bucket;
console.log('JSON MEDIACONVERT DATA', convertData);
const response = await MediaConvert.createJob(convertData).promise();
console.log('MediaConvert Job created:', response.Job);
return true;
} catch (error) {
console.error(error);
return false;
}
};
Phần coding đã xong bây giờ tiếp tục với phần config trên cloudformation để tạo những dịch vụ theo diagram
Edit file: amplify/backend/function/videoConvert/videoConvert-cloudformation-template.json
Giải thích:
LambdaFunction: dùng để build lambda function
DemoVideoConvertEventBridgeS3TriggerRule: dùng để build event bridge trigger khi có file được upload vào folder /video/
MyLambdaPermission: cho phép lambda function nhận event từ Event bridge
DemoVideoConvertEventBridgeProcessRule dùng để build event bridge trigger khi mediaconvert process thành công or thất bại
DemoVideoConvertSNSProcessTopic: dùng để build SNS thông báo trạng thái mediaconvert process thành công or thất bại từ Event Bridge
DemoVideoConvertSNSProcessTopicPolicy: cho phép SNS nhận event từ Event bridge
MyEmailSubscription: config email nhận thống báo Mediaconvert thành công or thất bại
MediaConvertExecutionRole: role lambda function
MediaConvertExecutionPolicy: các quyền mà lambda function thực thi
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Lambda Function resource stack creation using Amplify CLI",
"Parameters": {
"CloudWatchRule": {
"Type": "String",
"Default" : "NONE",
"Description" : " Schedule Expression"
},
"deploymentBucketName": {
"Type": "String"
},
"env": {
"Type": "String"
},
"s3Key": {
"Type": "String"
}
},
"Conditions": {
"ShouldNotCreateEnvResources": {
"Fn::Equals": [
{
"Ref": "env"
},
"NONE"
]
}
},
"Resources": {
"LambdaFunction": {
"Type": "AWS::Lambda::Function",
"Metadata": {
"aws:asset:path": "./src",
"aws:asset:property": "Code"
},
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "deploymentBucketName"
},
"S3Key": {
"Ref": "s3Key"
}
},
"Handler": "index.handler",
"FunctionName": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"demoVideoConvert",
{
"Fn::Join": [
"",
[
"demoVideoConvert",
"-",
{
"Ref": "env"
}
]
]
}
]
},
"Environment": {
"Variables": {
"ENV": {
"Ref": "env"
},
"REGION": {
"Ref": "AWS::Region"
},
"MEDIA_CONVERT_ROLE": {
"Fn::GetAtt": [
"MediaConvertExecutionRole",
"Arn"
]
}
}
},
"Role": {
"Fn::GetAtt": [
"LambdaExecutionRole",
"Arn"
]
},
"Runtime": "nodejs18.x",
"Layers": [
],
"MemorySize": 2048,
"EphemeralStorage": {
"Size": 2048
},
"Timeout": 300
}
},
"DemoVideoConvertEventBridgeS3TriggerRule": {
"DependsOn": [
"LambdaFunction"
],
"Type": "AWS::Events::Rule",
"Properties": {
"Name": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"DemoVideoConvertEventBridgeS3TriggerRule",
{
"Fn::Join": [
"",
[
"DemoVideoConvertEventBridgeS3TriggerRule",
"-",
{
"Ref": "env"
}
]
]
}
]
},
"Description": "Trigger Lambda from S3 events",
"EventPattern": {
"source": [
"aws.s3"
],
"detail-type": [
"Object Created"
],
"detail": {
"bucket": {
"name": [
"onetech-demo-mediaconvert"
]
},
"object": {
"key": [
{
"prefix": "video/"
}
]
}
}
},
"Targets": [
{
"Arn": {
"Fn::GetAtt": [
"LambdaFunction",
"Arn"
]
},
"Id": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"VideoAppEventBridgeRuleTargetId",
{
"Fn::Join": [
"",
[
"VideoAppEventBridgeRuleTargetId",
"-",
{
"Ref": "env"
}
]
]
}
]
}
}
]
}
},
"MyLambdaPermission": {
"DependsOn": [
"DemoVideoConvertEventBridgeS3TriggerRule"
],
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Fn::GetAtt": [
"LambdaFunction",
"Arn"
]
},
"Principal": "events.amazonaws.com",
"SourceArn": {
"Fn::GetAtt": [
"DemoVideoConvertEventBridgeS3TriggerRule",
"Arn"
]
}
}
},
"DemoVideoConvertEventBridgeProcessRule": {
"DependsOn": [
"DemoVideoConvertSNSProcessTopic"
],
"Type": "AWS::Events::Rule",
"Properties": {
"Name": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"DemoVideoConvertEventBridgeProcessRule",
{
"Fn::Join": [
"",
[
"DemoVideoConvertEventBridgeProcessRule",
"-",
{
"Ref": "env"
}
]
]
}
]
},
"Description": "Trigger mediaconvert process",
"EventPattern": {
"source": [
"aws.mediaconvert"
],
"detail": {
"status": [
"COMPLETE",
"ERROR"
],
"userMetadata": {
"environment": [
{
"Ref": "env"
}
]
}
}
},
"Targets": [
{
"Arn": {
"Ref": "DemoVideoConvertSNSProcessTopic"
},
"Id": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"VideoAppEventBridgeMediaconvertProcessRule",
{
"Fn::Join": [
"",
[
"VideoAppEventBridgeMediaconvertProcessRule",
"-",
{
"Ref": "env"
}
]
]
}
]
}
}
]
}
},
"DemoVideoConvertSNSProcessTopic": {
"DependsOn": [],
"Type": "AWS::SNS::Topic",
"Properties": {
"DisplayName": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"VideoAppSNSMediaconvertProcessTopic",
{
"Fn::Join": [
"",
[
"VideoAppSNSMediaconvertProcessTopic",
"-",
{
"Ref": "env"
}
]
]
}
]
}
}
},
"DemoVideoConvertSNSProcessTopicPolicy": {
"DependsOn": [
"DemoVideoConvertSNSProcessTopic"
],
"Type": "AWS::SNS::TopicPolicy",
"Properties": {
"Topics": [
{
"Ref": "DemoVideoConvertSNSProcessTopic"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowEventBridgePublish",
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
},
"Action": "sns:Publish",
"Resource": {
"Ref": "DemoVideoConvertSNSProcessTopic"
}
}
]
}
}
},
"MyEmailSubscription": {
"DependsOn": [
"DemoVideoConvertSNSProcessTopic"
],
"Type": "AWS::SNS::Subscription",
"Properties": {
"Protocol": "email",
"Endpoint": "duy@onetech.vn",
"TopicArn": {
"Ref": "DemoVideoConvertSNSProcessTopic"
}
}
},
"MediaConvertExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"videoAppMediaConvertRole",
{
"Fn::Join": [
"",
[
"videoAppMediaConvertRole",
"-",
{
"Ref": "env"
}
]
]
}
]
},
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"mediaconvert.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
}
}
},
"MediaConvertExecutionPolicy": {
"DependsOn": [
"MediaConvertExecutionRole"
],
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": "media-convert-execution-policy",
"Roles": [
{
"Ref": "MediaConvertExecutionRole"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*",
"s3-object-lambda:*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"execute-api:Invoke",
"execute-api:ManageConnections"
],
"Resource": "arn:aws:execute-api:*:*:*"
}
]
}
}
},
"LambdaExecutionRole": {
"DependsOn": [
"MediaConvertExecutionRole",
"MediaConvertExecutionPolicy"
],
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": {
"Fn::If": [
"ShouldNotCreateEnvResources",
"videoappconvertLambdaRole30cacecd",
{
"Fn::Join": [
"",
[
"videoappconvertLambdaRole30cacecd",
"-",
{
"Ref": "env"
}
]
]
}
]
},
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
}
}
},
"LambdaExecutionPolicy": {
"DependsOn": [
"LambdaExecutionRole"
],
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": "lambda-execution-policy",
"Roles": [
{
"Ref": "LambdaExecutionRole"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
{
"Fn::Sub": [
"arn:aws:logs:${region}:${account}:log-group:/aws/lambda/${lambda}:log-stream:*",
{
"region": {
"Ref": "AWS::Region"
},
"account": {
"Ref": "AWS::AccountId"
},
"lambda": {
"Ref": "LambdaFunction"
}
}
]
}
]
},
{
"Action": [
"iam:PassRole"
],
"Resource": [
{
"Fn::GetAtt": [
"MediaConvertExecutionRole",
"Arn"
]
}
],
"Effect": "Allow",
"Sid": "PassRole"
},
{
"Action": [
"mediaconvert:*"
],
"Resource": [
"*"
],
"Effect": "Allow",
"Sid": "MediaConvertService"
},
{
"Sid": "AllowS3Access",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "*"
}
]
}
}
}
},
"Outputs": {
"Name": {
"Value": {
"Ref": "LambdaFunction"
}
},
"Arn": {
"Value": {"Fn::GetAtt": ["LambdaFunction", "Arn"]}
},
"Region": {
"Value": {
"Ref": "AWS::Region"
}
},
"LambdaExecutionRole": {
"Value": {
"Ref": "LambdaExecutionRole"
}
}
}
}
Build lambda layer để thực thi thư viện ffmpeg
Run command: amplify add function
Chọn lambda layer
Name: videoConvertLayer
Node JS
Enter
Download thư viện ffmpeg folder /bin/ từ link này: https://github.com/phuocduy1988/ffmpeg
Sau khi download tiến hành copy folder bin/ vào thư mục:
amplify/backend/function/lambdamediaconvertvideoConvertLayer/opt/
Cài thư viện node JS:
“devDependencies”: {
“aws-sdk”: “^2.1416.0”,
“fluent-ffmpeg”: “^2.1.2”,
“fs”: “^0.0.1-security”,
“path”: “^0.12.7”
}
cd amplify/backend/function/lambdamediaconvertvideoConvertLayer/lib/nodejs
npm install aws-sdk fluent-ffmpeg fs path
Mục đích của việc cài thư viện này là để cho code bên lambda function sử dụng dạng common library.
Thêm lambda layer vào lambda mediaconvert
Run command: amplify update function
Lambda function
videoConvert
❯ Lambda layers configuration > Yes
Bấm space chọn > lambdamediaconvertvideoConvertLayer
Deploy source Amplify mediaconvert lên trên Aws cloud
Run command: amplify push > YES
Test chức năng mediaconvert
Vào AWS Console > Chọn region > S3 > onetech-demo-mediaconvert
> create folder video/ > upload video vào lên
Check cloudwatch log:
CloudWatch > Log groups> /aws/lambda/demoVideoConvert-dev
Vào folder convert-video/ kiểm tra xem file đã được convert thành công chưa
Như ta thấy file gốc từ 128MB giảm còn 9MB.
Chúc các bạn thành công!
Xoá những resource trên aws đã tạo:
Vào AWS Console > Chọn region > AWS Amplify > lambdamediaconvert > Chọn Delete app
Đợi xoá xong resource.
Xoá S3 bucket
Amazon S3 > Buckets > onetech-demo-mediaconvert > Empty bucket
Sau đó bắt đầu Delete bucket:
Tạm kết
Trên đây là hướng dẫn chi tiết cách sử dụng AWS Elemental MediaConvert để chuyển đổi video sang định dạng MP4, đồng thời nhận thông báo qua email về trạng thái hoàn thành hoặc lỗi.
Hy vọng bài viết này sẽ hữu ích cho các bạn đang tìm kiếm giải pháp chuyển đổi video hiệu quả.
OneTech Asia là công ty chuyên cung cấp các giải pháp phát triển phần mềm và dịch vụ công nghệ thông tin chất lượng cao. Chúng tôi tự hào về kinh nghiệm và chuyên môn trong việc phát triển lại các website và hệ thống web lớn cho các khách hàng trong và ngoài nước. Hãy liên hệ với chúng tôi để nhận được hỗ trợ tư vấn và đánh giá website nếu bạn đang có ý định xây dựng lại nhé!