본문 바로가기

Media Services/ElasticTranscoder

AWS Elastic Trancoder를 활용하여 HLS 서비스 하기

Elastic Transcoder를 이용하여 기존 스트리밍 서비스를 HLS 방식으로 서비스할 수 있습니다.

미디어 스트리밍은 크게 RTMP, RTSP, WMS, HLS(Http Live Streaming), HTTP Progressive 등으로 나눠집니다.


간단하게 스트리밍 프로토콜을 분류해서 설명하면,


RTMP: 모바일용 스트리밍

RTSP: PC용 스트리밍

WMS: Windows 기반 스트리밍

장점: 클라이언트 사이드에 스트리밍된 캐시 파일이 남지 않습니다. 또한 저화질, 고화질 등으로 파일을 트랜스코딩 하고, 사용자 Bandwidth에 맞게 해당 파일을 스티리밍할 수 있습니다. 

단점: Wowza와 같은 미디어 서버와 Client Side에 플레이어가 필요합니다. 또한 80 포트만 열려 있는 사내 서비스로 사용하기 어렵습니다.


HLS: HTTP 기반 스트리밍

HTTP Progressive: HTTP 기반 스트리밍

장점: 해당 프로토콜 또한 저화질, 고화질 등으로 파일을 트랜스코딩 하고, 사용자의 Request Header(mobile, PC 등)에 따라 스트리밍할 수 있습니다. Apache, nginx 등 웹 서버를 통해 서비스할 수 있습니다. 따라서 CloudFront 뿐만 아니라 CDN 등 활용이 가능합니다. 또한 사용자가 원하는 구간만 Download 되기 때문에 Traffic 비용을 절감할 수 있습니다.

단점: 스트리밍된 파일이 사용자 캐시에 남습니다. HLS의 경우 브라우저, 모바일 등 버전 특성을 타기 때문에 일부 사용자 스트리밍이 불가능합니다. (오래된 i.e, Android 버전 의 경우)


HLS는 미디어 파일을 ts라는 청크 파일로 만들고, 해당 청크 파일 목록은 m3u8이라는 파일로 관리됩니다. 

가령 http://test-streaming.com/test.m3u8 을 접근하면 test.m3u8 같은 경로에 000.ts, 001.ts 등 파일이 존재하고 m3u8은 해당 리스트를 관리하여 Client에 서비스합니다.

1
2
3
4
5
6
7
8
9
10
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:13
#EXTINF:12.066667,
20160326_xxx00000.ts
#EXTINF:9.000000,
20160326_xxx00001.ts
#EXTINF:9.000000,
cs


오늘은 mp4, wmv 등 파일을 Elastic Transcoder로 서비스 하기 위한 Preset과 Lambda sample code를 확인해봅니다.

가로, 세로 해상도가 각기 다른 파일들을 하나의 Preset으로 트랜스코딩 할 수 있는 옵션은 아래와 같습니다.

원본 해상도는 유지하고 bit rate를 5Mbps, Frame Rate는 30 등 세부 설정을 합니다. 

서비스는 모바일/PC용으로 분리하여 2/5Mbps로 각각 custom Preset을 생성합니다.

참고 - http://docs.aws.amazon.com/ko_kr/elastictranscoder/latest/developerguide/preset-settings.html


(ETS의 경우 서울 리전에 없기 때문에 Lambda. ETS, S3 Input은 도쿄 리전, ouput S3의 경우 서울 리전 구성)


1. 트랜스코딩할 Input, Output 버킷 생성. ETS의 Preset 설정(트랜스코딩 설정), Pipelines(S3 버킷 설정), Jobs(실행) 하여 수동으로 ETS를 사용해봅니다. 사용 방법 숙지 필요.


2. Lambda 코드를 생성합니다. (S3, ETS Role을 미리 생성해놓아야 합니다.)

아래는 Lambda Sample Code입니다. (Pipelin, preset ID만 매핑시켜주면 됩니다. Jobs을 Lambda에서 실행.)

이전 Elastic Transcoding 블로깅을 참고해서 아래 코드를 매핑시키고 Prefix만 바꿔준 뒤에 해당 S3 Bucket을 CloudFront의 원본으로 지정하면 HLS를 사용한 글로벌 미디어 서비스가 가능합니다. 

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
var aws = require('aws-sdk');
var elastictranscoder = new aws.ElasticTranscoder();
var s3 = new aws.S3();
var filename = function(path){ //upload object filename
    var pathSplit = path.split('.')[0];
    var arraySize = pathSplit.split('/').length-1;
    var filename = pathSplit.split('/')[arraySize];
    return filename;
}
var prefix = function(path){ //upload object prefix
    var pathSplit = path.split('.')[0];
    var arraySize = pathSplit.split('/').length-1;
    var prefix = '';
    for(var i=0; i<arraySize; i++){
        var text = pathSplit.split('/')[i];
        prefix=prefix.concat(text)+'/';
    }
    return prefix;
}
var listObject = function(params, callback){
    s3.listObjects(params, function(err, data){
        params.Delete = {Objects:[]};
        if(err) console.log(err, err.stack);
        else{
            console.log('listing Object...');
            data.Contents.forEach(function(result){
                params.Delete.Objects.push({Key: result.Key});
            })
            delete params.Prefix;
            callback(params);
            return
        }
    });
}
var deleteObject = function(params){
    listObject(params, function(deleteParams){
        console.log(deleteParams);
        s3.deleteObjects(deleteParams, function(err, data){
            if (err) console.log(err, err.stack); // an error occurreddeleteObjcects 
            else console.log(data);           // successful response
            console.log('delete Object...');
        });
    });
}
exports.handler = function(event, context) {
    console.log('Received event:', JSON.stringify(eventnull2));
    // Get the object from the event and show its content type
    var key = event.Records[0].s3.object.key;
    var s3Params_5m = {
        Bucket: '5m_media-transcoding'//output bucket name
        Prefix: prefix(key) + filename(key) + '_5M.' + key.split('.')[1+ '/' //S3 In/Output Object Prefix
    };
    var s3Params_2m = {
        Bucket: '2m_media-transcoding'//output bucket name
        Prefix: prefix(key) + filename(key) + '_2M.' + key.split('.')[1+ '/'
    };
 
    deleteObject(s3Params_5m);
    deleteObject(s3Params_2m);
 
    var etsParams_5m = {
        Input: {
        Key: key
        },
        PipelineId: 'xxx-4o47oe'// pipeline ID
        OutputKeyPrefix: prefix(key) + filename(key) + '_5m.' + key.split('.')[1+ '/',
        Outputs: [
            {
                Key: filename(key),
                PresetId: 'xxxxxx-97mimb',
                SegmentDuration: '10'
            }
        ],
        Playlists: [
            {
                Format: 'HLSv3',
                Name: 'playlist',
                OutputKeys: [
                filename(key)
                ]
            }
        ]
    };
    var etsParams_2m = {
        Input: {
        Key: key
        },
        PipelineId: 'xxx-4o47oe'// pipeline ID
        OutputKeyPrefix: prefix(key) + filename(key) + '_2m.' + key.split('.')[1+ '/',
        Outputs: [
            {
                Key: filename(key),
                PresetId:'xxxxxx-5nj36s'//default copy
                SegmentDuration: '10'
            }
        ],
        Playlists: [
            {
                Format: 'HLSv3',
                Name: 'playlist',
                OutputKeys: [
                filename(key)
                ]
            }
        ]
    };
 
 
    elastictranscoder.createJob(etsParams_5m, function(err, data) {
        if(err){
            console.log('transcoding_5m failed');
            console.log(err, err.stack); // an error occurred
            context.fail();
        }else{
            console.log('transcoding_5m succeed')
            context.succeed('transcoding_5m succeed');
        }
    });
    elastictranscoder.createJob(etsParams_2m, function(err, data) {
        if(err){
            console.log('transcoding_2m failed');
            console.log(err, err.stack); // an error occurred
            context.fail();
        }else{
            console.log('transcoding_2m succeed')
            context.succeed('transcoding_2m succeed');
        }
    });
};
cs


HLS의 경우 안드로이드 구형 버전(테스트는 갤럭시S3, NOTE3 Android 4.3.3)의 경우 내장 브라우저에서 재생이 안 되는 이슈가 있으므로, jwplayer와 같은 플레이어 모듈을 사용해야 합니다.


감사합니다.