设计说明
源码说明
本模块源码位于 source/artinchip/aic-mpp/middle_media
下,目录结构如下:
aic-mpp
|--middle-media
|-base
| |-inlcude
| | |-aic_message.h // 定义公共消息接口
| | |-aic_muxer.h // 定义 muxer 接口
| | |-aic_stream.h // 定义 stream 接口
| |
| |-message // 消息接口具体实现
| |-muxer // muxer 接口具体实现
| |-stream // stream 接口具体实现
|
|-component // Recorder 组件
| |-src // middle media component 各组件具体实现
| | |-mm_muxer_component.c // 封装组件,编码后封装成指定格式
| | |-mm_venc_component.c // 视频编码组件,输入 YUV 数据,输出编码后的视频图像
| | |-mm_vin_component.c // 视频输入组件,支持 DVP 和 文件输入
| |-mm_core.c // middle media component 公共核心接口具体实现
| |-inlcude // middle media component 公共头文件
| |-mm_component.h // middle media 各组件通用接口
| |-mm_core.h // middle media 组件对外核心接口
| |-mm_index.h // middle media 组件公共结构体
|
|-recorder
|-inlcude
| |-aic_recorder.h // recorder 接口定义
|
|aic_recorder.c // recorder 接口实现
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
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
模块架构
recorder 在系统架构中的层次如下图所示:
- recorder 是中间件,向 App 提供 recorder 接口。- recorder 视频编码调用 mjpeg_encoder
- recorder 音频编码调用 暂未实现
- recorder 视频输入操作 DVP driver 和 MPP VIN 提供的接口
- recorder 音频输入操作 暂未实现
- recorder 内部实现封装的功能
recorder 模块架构如下图,分为 3 层:
- recorder第 1 层,recorder interfacerecorder,向上提供 recorder 接口,向下调用 mm 组件实现 recorder 功能。
- recorder第 2 层,componentrecorder,提供统一的组件操作接口。每个组件完成特定的功能,比如视频编码、视频渲染等。
- recorder第 3 层,Baserecorder,提供 muxer、stream、message 接口和实现。
recorder 提供封装的功能,比如 MP4 封装。
stream 提供封装流协议的功能,本地文件也看作是一种流协议,目前也只支持本地文件。
message 为组件传递消息提供支持。
目前支持 MP4 封装的本地文件,后续扩展其他流协议和封装格式,通过增加相应的 recorder 和 stream 实现。
recorder interface 的设计
这一层是对外提供的接口,用户只与这一层交互即可。
数据结构设计
c
// 视频编码配置
struct video_encoding_config {
enum mpp_codec_type codec_type;
s32 out_width;
s32 out_height;
s32 out_bit_rate;
s32 out_frame_rate;
s32 out_qfactor;
s32 in_width;
s32 in_height;
s32 in_pix_fomat;
};
// 音频编码配置
struct audio_encoding_config {
enum aic_audio_codec_type codec_type;
int out_bitrate;
int out_samplerate;
int out_channels;
int out_bits_per_sample;
int in_samplerate;
int in_channels;
int in_bits_per_sample;
};
// 录像配置
struct aic_recorder_config {
int file_duration; // 单位:秒
int file_num; // 0-loop, >0 记录 file_num 然后停止录制
int file_muxer_type; // 仅支持 MP4
int qfactor;
s8 has_video;
s8 has_audio;
struct audio_encoding_config audio_config;
struct video_encoding_config video_config;
};
// 抓拍信息
struct aic_record_snapshot_info {
s8 *file_path;
};
// 录像事件类型
enum aic_recorder_event {
AIC_RECORDER_EVENT_NEED_NEXT_FILE = 0,
AIC_RECORDER_EVENT_COMPLETE,
AIC_RECORDER_EVENT_NO_SPACE,
AIC_RECORDER_EVENT_RELEASE_VIDEO_BUFFER
};
// 视频输入源
enum aic_recorder_vin_type {
AIC_RECORDER_VIN_FILE = 0,
AIC_RECORDER_VIN_DVP,
AIC_RECORDER_VIN_USB
};
// 事件回调函数
typedef s32 (*event_handler)(void *app_data, s32 event, s32 data1, s32 data2);
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
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
接口设计
c
struct aic_recorder *aic_recorder_create(void);
s32 aic_recorder_destroy(struct aic_recorder *recorder);
s32 aic_recorder_set_event_callback(struct aic_recorder *recorder, void *app_data, event_handler event_handle);
s32 aic_recorder_set_input_file_path(struct aic_recorder *recorder, char *video_uri, char *audio_uri);
s32 aic_recorder_set_output_file_path(struct aic_recorder *recorder, char *uri);
s32 aic_recorder_init(struct aic_recorder *recorder, struct aic_recorder_config *recorder_config);
s32 aic_recorder_start(struct aic_recorder *recorder);
s32 aic_recorder_stop(struct aic_recorder *recorder);
s32 aic_recorder_snapshot(struct aic_recorder *recorder, struct aic_record_snapshot_info *snapshot_info);
s32 aic_recorder_set_vin_type(struct aic_recorder *recorder, enum aic_recorder_vin_type type);
s32 aic_recorder_print_debug_info(struct aic_recorder *recorder);
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
aic_recorder_create
函数原型 | struct aic_recorder *aic_recorder_create(void) |
---|---|
功能说明 | 创建 recorder 对象 |
参数定义 | 无 |
返回值 | recorder 对象 |
注意事项 | 无 |
aic_recorder_destroy
函数原型 | s32 aic_recorder_destroy(struct aic_recorder *recorder) |
---|---|
功能说明 | 销毁 recorder 对象 |
参数定义 | recorder:recorder 对象 |
返回值 | 0:成功,其他:失败 |
注意事项 | 在 IDLE 和 STOPPED 状态起作用 |
aic_recorder_init
函数原型 | s32 aic_recorder_init(struct aic_recorder *recorder, struct aic_recorder_config *recorder_config) |
---|---|
功能说明 | 初始化 recorder 对象 |
参数定义 | recorder:recorder 对象, recorder_config:recorder 配置 |
返回值 | 0:成功,其他:失败 |
注意事项 | 在 IDLE 和 STOPPED 状态起作用 |
aic_recorder_start
函数原型 | s32 aic_recorder_start(struct aic_recorder *recorder) |
---|---|
功能说明 | 启动录像,创建音视频编码线程和音视频输入线程 |
参数定义 | recorder:recorder 对象 |
返回值 | 0:成功,其他:失败 |
注意事项 | 在 PREPARED 状态起作用 |
aic_recorder_stop
函数原型 | s32 aic_recorder_stop(struct aic_recorder *recorder) |
---|---|
功能说明 | 停止录像,释放音视频编码线程和音视频输入线程 |
参数定义 | recorder:recorder 对象 |
返回值 | 0:成功,其他:失败 |
注意事项 | 任意状态可以调用该函数来停止录像。切换文件时一定要调用 aic_recorder_stop 。 |
aic_recorder_set_input_file_path
函数原型 | s32 aic_recorder_set_input_file_path(struct aic_recorder *recorder, char *video_uri, char *audio_uri) |
---|---|
功能说明 | 视频输入源为文件时,设置文件路径 |
参数定义 | recorder:recorder 对象,video_uri:视频源文件路径,audio_uri:音频源文件路径 |
返回值 | 0:成功,其他:失败 |
注意事项 | 在 IDLE 和 STOPPED 状态起作用 |
aic_recorder_set_output_file_path¶
函数原型 | s32 aic_recorder_set_output_file_path(struct aic_recorder *recorder, char *uri) |
---|---|
功能说明 | 设置输出录像文件路径 |
参数定义 | recorder:recorder 对象,uri:媒体文件路径 |
返回值 | 0:成功,其他:失败 |
注意事项 | 在 IDLE 和 STOPPED 状态起作用 |
aic_recorder_set_event_callback
函数原型 | s32 aic_recorder_set_event_callback(struct aic_recorder *recorder, event_handler event_handle) |
---|---|
功能说明 | 设置录像事件回调函数 |
参数定义 | recorder:recorder 对象,event_handle:监听事件函数 |
返回值 | 0:成功,其他:失败 |
注意事项 | 一定要注册,录像结束通过回调函数通知 |
aic_recorder_snapshot
函数原型 | s32 aic_recorder_snapshot(struct aic_recorder *recorder, struct aic_record_snapshot_info *snapshot_info) |
---|---|
功能说明 | 触发一次抓拍事件 |
参数定义 | recorder:recorder 对象,snapshot_info:抓拍信息 |
返回值 | 0:成功,其他:失败 |
注意事项 | 启动录像期间抓拍 |
aic_recorder_set_vin_type
函数原型 | s32 aic_recorder_set_vin_type(struct aic_recorder *recorder, enum aic_recorder_vin_type type); |
---|---|
功能说明 | 设置视频输入源类型 |
参数定义 | recorder:recorder 对象,type:视频输入源类型 |
返回值 | 0:成功,其他:失败 |
注意事项 | 无 |
APP Demo
bash
static void print_help(const char* prog)
{
printf("name: %s\n", prog);
printf("Compile time: %s\n", __TIME__);
printf("Usage: recoder_demo [options]:\n"
"\t-i video input source\n"
"\t-c recoder config file\n"
"\t-h help\n\n"
"Example1: recoder_demo -i dvp -c /sdcard/recoder.json\n"
"Example2: recoder_demo -i file -c /sdcard/recoder.json\n");
}
char *read_file(const char *filename)
{
FILE *file = NULL;
long length = 0;
char *content = NULL;
size_t read_chars = 0;
/* open in read binary mode */
file = fopen(filename, "rb");
if (file == NULL) {
goto cleanup;
}
/* get the length */
if (fseek(file, 0, SEEK_END) != 0) {
goto cleanup;
}
length = ftell(file);
if (length < 0) {
goto cleanup;
}
if (fseek(file, 0, SEEK_SET) != 0) {
goto cleanup;
}
/* allocate content buffer */
content = (char *)malloc((size_t)length + sizeof(""));
if (content == NULL) {
goto cleanup;
}
/* read the file into memory */
read_chars = fread(content, sizeof(char), (size_t)length, file);
if ((long)read_chars != length) {
free(content);
content = NULL;
goto cleanup;
}
content[read_chars] = '\0';
cleanup:
if (file != NULL) {
fclose(file);
}
return content;
}
static cJSON *parse_file(const char *filename)
{
cJSON *parsed = NULL;
char *content = read_file(filename);
parsed = cJSON_Parse(content);
if (content != NULL) {
free(content);
}
return parsed;
}
struct recorder_context {
struct aic_recorder *recorder;
enum aic_recorder_vin_type vin_source_type;
char config_file_path[256];
char video_in_file_path[256];
char audio_in_file_path[256];
char output_file_path[256];
char capture_file_path[256];
struct aic_recorder_config config;
};
static int g_recorder_flag = 0;
static struct recorder_context *g_recorder_cxt = NULL;
static s32 event_handle(void *app_data, s32 event, s32 data1, s32 data2)
{
int ret = 0;
struct recorder_context *recorder_cxt = (struct recorder_context *)app_data;
static int file_index = 0;
char file_path[512] = {0};
switch (event) {
case AIC_RECORDER_EVENT_NEED_NEXT_FILE:
// set recorder file name
snprintf(file_path, sizeof(file_path), "%s-%d.mp4",
recorder_cxt->output_file_path, file_index++);
aic_recorder_set_output_file_path(recorder_cxt->recorder, file_path);
printf("set recorder file:%s\n", file_path);
break;
case AIC_RECORDER_EVENT_COMPLETE:
g_recorder_flag = 1;
break;
case AIC_RECORDER_EVENT_NO_SPACE:
break;
default:
break;
}
return ret;
}
int parse_config_file(char *config_file, struct recorder_context *recorder_cxt)
{
int ret = 0;
cJSON *cjson = NULL;
cJSON *root = NULL;
if (!config_file || !recorder_cxt) {
ret = -1;
goto _EXIT;
}
root = parse_file(config_file);
if (!root) {
loge("parse_file error %s!!!", config_file);
ret = -1;
goto _EXIT;
}
cjson = cJSON_GetObjectItem(root, "video_in_file");
if (!cjson) {
loge("no video_in_file error");
ret = -1;
goto _EXIT;
}
strcpy(recorder_cxt->video_in_file_path, cjson->valuestring);
cjson = cJSON_GetObjectItem(root, "audio_in_file");
if (!cjson) {
strcpy(recorder_cxt->audio_in_file_path, cjson->valuestring);
}
cjson = cJSON_GetObjectItem(root, "output_file");
if (!cjson) {
loge("no output_file error");
ret = -1;
goto _EXIT;
}
strcpy(recorder_cxt->output_file_path, cjson->valuestring);
cjson = cJSON_GetObjectItem(root, "file_duration");
if (cjson) {
recorder_cxt->config.file_duration = cjson->valueint * 1000;
}
cjson = cJSON_GetObjectItem(root, "file_num");
if (cjson) {
recorder_cxt->config.file_num = cjson->valueint;
}
cjson = cJSON_GetObjectItem(root, "qfactor");
if (cjson) {
recorder_cxt->config.qfactor = cjson->valueint;
}
cjson = cJSON_GetObjectItem(root, "video");
if (cjson) {
int enable = cJSON_GetObjectItem(cjson, "enable")->valueint;
if (enable == 1) {
recorder_cxt->config.has_video = 1;
}
recorder_cxt->config.video_config.codec_type =
cJSON_GetObjectItem(cjson, "codec_type")->valueint;
printf("codec_type:0x%x\n", recorder_cxt->config.video_config.codec_type);
if (recorder_cxt->config.video_config.codec_type != MPP_CODEC_VIDEO_DECODER_MJPEG) {
ret = -1;
loge("only support MPP_CODEC_VIDEO_DECODER_MJPEG");
g_recorder_flag = 1;
goto _EXIT;
}
recorder_cxt->config.video_config.out_width =
cJSON_GetObjectItem(cjson, "out_width")->valueint;
recorder_cxt->config.video_config.out_height =
cJSON_GetObjectItem(cjson, "out_height")->valueint;
recorder_cxt->config.video_config.out_frame_rate =
cJSON_GetObjectItem(cjson, "out_framerate")->valueint;
recorder_cxt->config.video_config.out_bit_rate =
cJSON_GetObjectItem(cjson, "out_bitrate")->valueint;
recorder_cxt->config.video_config.in_width =
cJSON_GetObjectItem(cjson, "in_width")->valueint;
recorder_cxt->config.video_config.in_height =
cJSON_GetObjectItem(cjson, "in_height")->valueint;
recorder_cxt->config.video_config.in_pix_fomat =
cJSON_GetObjectItem(cjson, "in_pix_format")->valueint;
}
cjson = cJSON_GetObjectItem(root, "audio");
if (cjson) {
int enable = cJSON_GetObjectItem(cjson, "enable")->valueint;
if (enable == 1) {
recorder_cxt->config.has_audio = 1;
}
recorder_cxt->config.audio_config.codec_type =
cJSON_GetObjectItem(cjson, "codec_type")->valueint;
recorder_cxt->config.audio_config.out_bitrate =
cJSON_GetObjectItem(cjson, "out_bitrate")->valueint;
recorder_cxt->config.audio_config.out_samplerate =
cJSON_GetObjectItem(cjson, "out_samplerate")->valueint;
recorder_cxt->config.audio_config.out_channels =
cJSON_GetObjectItem(cjson, "out_channels")->valueint;
recorder_cxt->config.audio_config.out_bits_per_sample =
cJSON_GetObjectItem(cjson, "out_bits_per_sample")->valueint;
recorder_cxt->config.audio_config.in_samplerate =
cJSON_GetObjectItem(cjson, "in_samplerate")->valueint;
recorder_cxt->config.audio_config.in_channels =
cJSON_GetObjectItem(cjson, "in_channels")->valueint;
recorder_cxt->config.audio_config.in_bits_per_sample =
cJSON_GetObjectItem(cjson, "in_bits_per_sample")->valueint;
}
_EXIT:
if (root) {
cJSON_Delete(root);
}
return ret;
}
static int parse_options(struct recorder_context *recoder_ctx, int cnt, char **options)
{
int argc = cnt;
char **argv = options;
struct recorder_context *ctx = recoder_ctx;
int opt;
if (!ctx || argc == 0 || !argv) {
loge("para error !!!");
return -1;
}
optind = 0;
while (1) {
opt = getopt(argc, argv, "i:c:h");
if (opt == -1) {
break;
}
switch (opt) {
case 'i':
if (strcmp(optarg, "dvp") == 0) {
ctx->vin_source_type = AIC_RECORDER_VIN_DVP;
} else {
ctx->vin_source_type = AIC_RECORDER_VIN_FILE;
}
break;
case 'c':
strcpy(ctx->config_file_path, optarg);
break;
case 'h':
default:
print_help(argv[0]);
return -1;
}
}
return 0;
}
static void show_cpu_usage()
{
#if defined(LPKG_USING_CPU_USAGE) && defined(RECODER_DEMO_PRINT_CPU)
static int index = 0;
char data_str[64];
float value = 0.0;
if (index++ % 30 == 0) {
value = cpu_load_average();
#ifdef AIC_PRINT_FLOAT_CUSTOM
int cpu_i;
int cpu_frac;
cpu_i = (int)value;
cpu_frac = (value - cpu_i) * 100;
snprintf(data_str, sizeof(data_str), "%d.%02d\n", cpu_i, cpu_frac);
#else
snprintf(data_str, sizeof(data_str), "%.2f\n", value);
#endif
printf("cpu_loading:%s\n",data_str);
}
#endif
}
static void *test_recorder_thread(void *arg)
{
struct recorder_context *recorder_cxt = (struct recorder_context *)arg;
while (!g_recorder_flag) {
show_cpu_usage();
usleep(100*1000);
}
if (recorder_cxt && recorder_cxt->recorder) {
aic_recorder_stop(recorder_cxt->recorder);
aic_recorder_destroy(recorder_cxt->recorder);
recorder_cxt->recorder = NULL;
}
if (recorder_cxt) {
free(recorder_cxt);
recorder_cxt = NULL;
}
printf("test_recorder_thread exit\n");
return NULL;
}
#define BUFFER_LEN 32
int recorder_demo_test(int argc, char *argv[])
{
int ret = 0;
pthread_attr_t attr;
pthread_t thread_id;
struct recorder_context *recorder_cxt = NULL;
g_recorder_flag = 0;
recorder_cxt = malloc(sizeof(struct recorder_context));
if (!recorder_cxt) {
loge("malloc error");
return -1;
}
memset(recorder_cxt, 0x00, sizeof(struct recorder_context));
if (parse_options(recorder_cxt, argc, argv)) {
goto _EXIT;
}
g_recorder_cxt = recorder_cxt;
if (parse_config_file(recorder_cxt->config_file_path, recorder_cxt)) {
loge("parse_config_file %s error", recorder_cxt->config_file_path);
goto _EXIT;
}
#ifdef _THREAD_TRACE_INFO_
memset(&thread_trace_infos,0x00,sizeof(struct thread_trace_info));
for (int i = 0; i < 6 ;i++) {
snprintf(thread_trace_infos[i].thread_name,sizeof(thread_trace_infos[i].thread_name),"%s%02d","pth",i);
printf("%s\n",thread_trace_infos[i].thread_name);
}
rt_scheduler_sethook(hook_of_scheduler);
#endif
recorder_cxt->recorder = aic_recorder_create();
if (!recorder_cxt->recorder) {
loge("aic_recorder_create error");
goto _EXIT;
}
if (aic_recorder_set_event_callback(recorder_cxt->recorder,
recorder_cxt, event_handle)) {
loge("aic_recorder_set_event_callback error");
goto _EXIT;
}
if (aic_recorder_init(recorder_cxt->recorder, &recorder_cxt->config)) {
loge("aic_recorder_init error");
goto _EXIT;
}
aic_recorder_set_vin_type(recorder_cxt->recorder, recorder_cxt->vin_source_type);
aic_recorder_set_input_file_path(recorder_cxt->recorder, recorder_cxt->video_in_file_path, NULL);
if (aic_recorder_start(recorder_cxt->recorder)) {
loge("aic_recorder_start error");
goto _EXIT;
}
pthread_attr_init(&attr);
attr.stacksize = 2 * 1024;
attr.schedparam.sched_priority = 30;
ret = pthread_create(&thread_id, &attr, test_recorder_thread, recorder_cxt);
if (ret) {
loge("create test_recorder_thread failed\n");
}
#include "msh.h"
msh_exec("ps \n", strlen("ps \n"));
return ret;
_EXIT:
if (recorder_cxt && recorder_cxt->recorder) {
aic_recorder_stop(recorder_cxt->recorder);
aic_recorder_destroy(recorder_cxt->recorder);
recorder_cxt->recorder = NULL;
}
if (recorder_cxt) {
free(recorder_cxt);
recorder_cxt = NULL;
}
return ret;
}
MSH_CMD_EXPORT_ALIAS(recorder_demo_test, recorder_demo, recorder demo);
int recorder_demo_cmd(int argc, char *argv[])
{
int ret = 0;
static int capture_count = 0;
struct recorder_context *recorder_cxt = g_recorder_cxt;
if (argc < 1) {
return -1;
}
if (strcmp(argv[1], "stop") == 0) {
g_recorder_flag = 1;
} else if (strcmp(argv[1], "snap") == 0) {
struct aic_record_snapshot_info snap_info;
snprintf(recorder_cxt->capture_file_path, sizeof(recorder_cxt->capture_file_path),
"/sdcard/capture-%d.jpg", capture_count++);
snap_info.file_path = (s8 *)recorder_cxt->capture_file_path;
aic_recorder_snapshot(recorder_cxt->recorder, &snap_info);
} else if (strcmp(argv[1], "debug") == 0) {
aic_recorder_print_debug_info(recorder_cxt->recorder);
}
return ret;
}
MSH_CMD_EXPORT_ALIAS(recorder_demo_cmd, recorder_demo_cmd, recorder demo cmd);
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432