Mercurial > libavcodec.hg
annotate mjpegenc.c @ 10598:5e566408864c libavcodec
nellymoser: use constant seed for dithering RNG
author | mru |
---|---|
date | Sun, 29 Nov 2009 15:15:53 +0000 |
parents | bf04c3ecdfe5 |
children | 7127645ee791 |
rev | line source |
---|---|
5020 | 1 /* |
2 * MJPEG encoder | |
8629
04423b2f6e0b
cosmetics: Remove pointless period after copyright statement non-sentences.
diego
parents:
7260
diff
changeset
|
3 * Copyright (c) 2000, 2001 Fabrice Bellard |
5020 | 4 * Copyright (c) 2003 Alex Beregszaszi |
5 * Copyright (c) 2003-2004 Michael Niedermayer | |
6 * | |
5214 | 7 * Support for external huffman table, various fixes (AVID workaround), |
8 * aspecting, new decode_frame mechanism and apple mjpeg-b support | |
9 * by Alex Beregszaszi | |
10 * | |
5020 | 11 * This file is part of FFmpeg. |
12 * | |
13 * FFmpeg is free software; you can redistribute it and/or | |
14 * modify it under the terms of the GNU Lesser General Public | |
15 * License as published by the Free Software Foundation; either | |
16 * version 2.1 of the License, or (at your option) any later version. | |
17 * | |
18 * FFmpeg is distributed in the hope that it will be useful, | |
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
21 * Lesser General Public License for more details. | |
22 * | |
23 * You should have received a copy of the GNU Lesser General Public | |
24 * License along with FFmpeg; if not, write to the Free Software | |
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
26 */ | |
27 | |
28 /** | |
8718
e9d9d946f213
Use full internal pathname in doxygen @file directives.
diego
parents:
8629
diff
changeset
|
29 * @file libavcodec/mjpegenc.c |
5020 | 30 * MJPEG encoder. |
31 */ | |
32 | |
33 //#define DEBUG | |
34 #include <assert.h> | |
35 | |
36 #include "avcodec.h" | |
37 #include "dsputil.h" | |
38 #include "mpegvideo.h" | |
39 #include "mjpeg.h" | |
5028 | 40 #include "mjpegenc.h" |
5020 | 41 |
42 /* use two quantizer tables (one for luminance and one for chrominance) */ | |
43 /* not yet working */ | |
44 #undef TWOMATRIXES | |
45 | |
46 | |
6517
48759bfbd073
Apply 'cold' attribute to init/uninit functions in libavcodec
zuxy
parents:
5214
diff
changeset
|
47 av_cold int ff_mjpeg_encode_init(MpegEncContext *s) |
5020 | 48 { |
49 MJpegContext *m; | |
50 | |
51 m = av_malloc(sizeof(MJpegContext)); | |
52 if (!m) | |
53 return -1; | |
54 | |
55 s->min_qcoeff=-1023; | |
56 s->max_qcoeff= 1023; | |
57 | |
58 /* build all the huffman tables */ | |
5021 | 59 ff_mjpeg_build_huffman_codes(m->huff_size_dc_luminance, |
60 m->huff_code_dc_luminance, | |
61 ff_mjpeg_bits_dc_luminance, | |
7136 | 62 ff_mjpeg_val_dc); |
5021 | 63 ff_mjpeg_build_huffman_codes(m->huff_size_dc_chrominance, |
64 m->huff_code_dc_chrominance, | |
65 ff_mjpeg_bits_dc_chrominance, | |
7136 | 66 ff_mjpeg_val_dc); |
5021 | 67 ff_mjpeg_build_huffman_codes(m->huff_size_ac_luminance, |
68 m->huff_code_ac_luminance, | |
69 ff_mjpeg_bits_ac_luminance, | |
70 ff_mjpeg_val_ac_luminance); | |
71 ff_mjpeg_build_huffman_codes(m->huff_size_ac_chrominance, | |
72 m->huff_code_ac_chrominance, | |
73 ff_mjpeg_bits_ac_chrominance, | |
74 ff_mjpeg_val_ac_chrominance); | |
5020 | 75 |
76 s->mjpeg_ctx = m; | |
77 return 0; | |
78 } | |
79 | |
5029
dbaa06366c3c
add a proper prefix to all mjpeg encoder exported functions
aurel
parents:
5028
diff
changeset
|
80 void ff_mjpeg_encode_close(MpegEncContext *s) |
5020 | 81 { |
82 av_free(s->mjpeg_ctx); | |
83 } | |
84 | |
85 /* table_class: 0 = DC coef, 1 = AC coefs */ | |
86 static int put_huffman_table(MpegEncContext *s, int table_class, int table_id, | |
87 const uint8_t *bits_table, const uint8_t *value_table) | |
88 { | |
89 PutBitContext *p = &s->pb; | |
90 int n, i; | |
91 | |
92 put_bits(p, 4, table_class); | |
93 put_bits(p, 4, table_id); | |
94 | |
95 n = 0; | |
96 for(i=1;i<=16;i++) { | |
97 n += bits_table[i]; | |
98 put_bits(p, 8, bits_table[i]); | |
99 } | |
100 | |
101 for(i=0;i<n;i++) | |
102 put_bits(p, 8, value_table[i]); | |
103 | |
104 return n + 17; | |
105 } | |
106 | |
107 static void jpeg_table_header(MpegEncContext *s) | |
108 { | |
109 PutBitContext *p = &s->pb; | |
110 int i, j, size; | |
111 uint8_t *ptr; | |
112 | |
113 /* quant matrixes */ | |
114 put_marker(p, DQT); | |
115 #ifdef TWOMATRIXES | |
116 put_bits(p, 16, 2 + 2 * (1 + 64)); | |
117 #else | |
118 put_bits(p, 16, 2 + 1 * (1 + 64)); | |
119 #endif | |
120 put_bits(p, 4, 0); /* 8 bit precision */ | |
121 put_bits(p, 4, 0); /* table 0 */ | |
122 for(i=0;i<64;i++) { | |
123 j = s->intra_scantable.permutated[i]; | |
124 put_bits(p, 8, s->intra_matrix[j]); | |
125 } | |
126 #ifdef TWOMATRIXES | |
127 put_bits(p, 4, 0); /* 8 bit precision */ | |
128 put_bits(p, 4, 1); /* table 1 */ | |
129 for(i=0;i<64;i++) { | |
130 j = s->intra_scantable.permutated[i]; | |
131 put_bits(p, 8, s->chroma_intra_matrix[j]); | |
132 } | |
133 #endif | |
134 | |
135 /* huffman table */ | |
136 put_marker(p, DHT); | |
137 flush_put_bits(p); | |
9431 | 138 ptr = put_bits_ptr(p); |
5020 | 139 put_bits(p, 16, 0); /* patched later */ |
140 size = 2; | |
5021 | 141 size += put_huffman_table(s, 0, 0, ff_mjpeg_bits_dc_luminance, |
7136 | 142 ff_mjpeg_val_dc); |
5021 | 143 size += put_huffman_table(s, 0, 1, ff_mjpeg_bits_dc_chrominance, |
7136 | 144 ff_mjpeg_val_dc); |
5020 | 145 |
5021 | 146 size += put_huffman_table(s, 1, 0, ff_mjpeg_bits_ac_luminance, |
147 ff_mjpeg_val_ac_luminance); | |
148 size += put_huffman_table(s, 1, 1, ff_mjpeg_bits_ac_chrominance, | |
149 ff_mjpeg_val_ac_chrominance); | |
5089 | 150 AV_WB16(ptr, size); |
5020 | 151 } |
152 | |
153 static void jpeg_put_comments(MpegEncContext *s) | |
154 { | |
155 PutBitContext *p = &s->pb; | |
156 int size; | |
157 uint8_t *ptr; | |
158 | |
159 if (s->aspect_ratio_info /* && !lossless */) | |
160 { | |
161 /* JFIF header */ | |
162 put_marker(p, APP0); | |
163 put_bits(p, 16, 16); | |
164 ff_put_string(p, "JFIF", 1); /* this puts the trailing zero-byte too */ | |
165 put_bits(p, 16, 0x0201); /* v 1.02 */ | |
166 put_bits(p, 8, 0); /* units type: 0 - aspect ratio */ | |
167 put_bits(p, 16, s->avctx->sample_aspect_ratio.num); | |
168 put_bits(p, 16, s->avctx->sample_aspect_ratio.den); | |
169 put_bits(p, 8, 0); /* thumbnail width */ | |
170 put_bits(p, 8, 0); /* thumbnail height */ | |
171 } | |
172 | |
173 /* comment */ | |
174 if(!(s->flags & CODEC_FLAG_BITEXACT)){ | |
175 put_marker(p, COM); | |
176 flush_put_bits(p); | |
9431 | 177 ptr = put_bits_ptr(p); |
5020 | 178 put_bits(p, 16, 0); /* patched later */ |
179 ff_put_string(p, LIBAVCODEC_IDENT, 1); | |
180 size = strlen(LIBAVCODEC_IDENT)+3; | |
5089 | 181 AV_WB16(ptr, size); |
5020 | 182 } |
183 | |
184 if( s->avctx->pix_fmt == PIX_FMT_YUV420P | |
185 ||s->avctx->pix_fmt == PIX_FMT_YUV422P | |
186 ||s->avctx->pix_fmt == PIX_FMT_YUV444P){ | |
187 put_marker(p, COM); | |
188 flush_put_bits(p); | |
9431 | 189 ptr = put_bits_ptr(p); |
5020 | 190 put_bits(p, 16, 0); /* patched later */ |
191 ff_put_string(p, "CS=ITU601", 1); | |
192 size = strlen("CS=ITU601")+3; | |
5089 | 193 AV_WB16(ptr, size); |
5020 | 194 } |
195 } | |
196 | |
5029
dbaa06366c3c
add a proper prefix to all mjpeg encoder exported functions
aurel
parents:
5028
diff
changeset
|
197 void ff_mjpeg_encode_picture_header(MpegEncContext *s) |
5020 | 198 { |
199 const int lossless= s->avctx->codec_id != CODEC_ID_MJPEG; | |
200 | |
201 put_marker(&s->pb, SOI); | |
202 | |
203 jpeg_put_comments(s); | |
204 | |
205 jpeg_table_header(s); | |
206 | |
207 switch(s->avctx->codec_id){ | |
208 case CODEC_ID_MJPEG: put_marker(&s->pb, SOF0 ); break; | |
209 case CODEC_ID_LJPEG: put_marker(&s->pb, SOF3 ); break; | |
210 default: assert(0); | |
211 } | |
212 | |
213 put_bits(&s->pb, 16, 17); | |
214 if(lossless && s->avctx->pix_fmt == PIX_FMT_RGB32) | |
215 put_bits(&s->pb, 8, 9); /* 9 bits/component RCT */ | |
216 else | |
217 put_bits(&s->pb, 8, 8); /* 8 bits/component */ | |
218 put_bits(&s->pb, 16, s->height); | |
219 put_bits(&s->pb, 16, s->width); | |
220 put_bits(&s->pb, 8, 3); /* 3 components */ | |
221 | |
222 /* Y component */ | |
223 put_bits(&s->pb, 8, 1); /* component number */ | |
224 put_bits(&s->pb, 4, s->mjpeg_hsample[0]); /* H factor */ | |
225 put_bits(&s->pb, 4, s->mjpeg_vsample[0]); /* V factor */ | |
226 put_bits(&s->pb, 8, 0); /* select matrix */ | |
227 | |
228 /* Cb component */ | |
229 put_bits(&s->pb, 8, 2); /* component number */ | |
230 put_bits(&s->pb, 4, s->mjpeg_hsample[1]); /* H factor */ | |
231 put_bits(&s->pb, 4, s->mjpeg_vsample[1]); /* V factor */ | |
232 #ifdef TWOMATRIXES | |
233 put_bits(&s->pb, 8, lossless ? 0 : 1); /* select matrix */ | |
234 #else | |
235 put_bits(&s->pb, 8, 0); /* select matrix */ | |
236 #endif | |
237 | |
238 /* Cr component */ | |
239 put_bits(&s->pb, 8, 3); /* component number */ | |
240 put_bits(&s->pb, 4, s->mjpeg_hsample[2]); /* H factor */ | |
241 put_bits(&s->pb, 4, s->mjpeg_vsample[2]); /* V factor */ | |
242 #ifdef TWOMATRIXES | |
243 put_bits(&s->pb, 8, lossless ? 0 : 1); /* select matrix */ | |
244 #else | |
245 put_bits(&s->pb, 8, 0); /* select matrix */ | |
246 #endif | |
247 | |
248 /* scan header */ | |
249 put_marker(&s->pb, SOS); | |
250 put_bits(&s->pb, 16, 12); /* length */ | |
251 put_bits(&s->pb, 8, 3); /* 3 components */ | |
252 | |
253 /* Y component */ | |
254 put_bits(&s->pb, 8, 1); /* index */ | |
255 put_bits(&s->pb, 4, 0); /* DC huffman table index */ | |
256 put_bits(&s->pb, 4, 0); /* AC huffman table index */ | |
257 | |
258 /* Cb component */ | |
259 put_bits(&s->pb, 8, 2); /* index */ | |
260 put_bits(&s->pb, 4, 1); /* DC huffman table index */ | |
261 put_bits(&s->pb, 4, lossless ? 0 : 1); /* AC huffman table index */ | |
262 | |
263 /* Cr component */ | |
264 put_bits(&s->pb, 8, 3); /* index */ | |
265 put_bits(&s->pb, 4, 1); /* DC huffman table index */ | |
266 put_bits(&s->pb, 4, lossless ? 0 : 1); /* AC huffman table index */ | |
267 | |
268 put_bits(&s->pb, 8, lossless ? s->avctx->prediction_method+1 : 0); /* Ss (not used) */ | |
269 | |
270 switch(s->avctx->codec_id){ | |
271 case CODEC_ID_MJPEG: put_bits(&s->pb, 8, 63); break; /* Se (not used) */ | |
272 case CODEC_ID_LJPEG: put_bits(&s->pb, 8, 0); break; /* not used */ | |
273 default: assert(0); | |
274 } | |
275 | |
276 put_bits(&s->pb, 8, 0); /* Ah/Al (not used) */ | |
277 } | |
278 | |
279 static void escape_FF(MpegEncContext *s, int start) | |
280 { | |
281 int size= put_bits_count(&s->pb) - start*8; | |
282 int i, ff_count; | |
283 uint8_t *buf= s->pb.buf + start; | |
284 int align= (-(size_t)(buf))&3; | |
285 | |
286 assert((size&7) == 0); | |
287 size >>= 3; | |
288 | |
289 ff_count=0; | |
290 for(i=0; i<size && i<align; i++){ | |
291 if(buf[i]==0xFF) ff_count++; | |
292 } | |
293 for(; i<size-15; i+=16){ | |
294 int acc, v; | |
295 | |
296 v= *(uint32_t*)(&buf[i]); | |
297 acc= (((v & (v>>4))&0x0F0F0F0F)+0x01010101)&0x10101010; | |
298 v= *(uint32_t*)(&buf[i+4]); | |
299 acc+=(((v & (v>>4))&0x0F0F0F0F)+0x01010101)&0x10101010; | |
300 v= *(uint32_t*)(&buf[i+8]); | |
301 acc+=(((v & (v>>4))&0x0F0F0F0F)+0x01010101)&0x10101010; | |
302 v= *(uint32_t*)(&buf[i+12]); | |
303 acc+=(((v & (v>>4))&0x0F0F0F0F)+0x01010101)&0x10101010; | |
304 | |
305 acc>>=4; | |
306 acc+= (acc>>16); | |
307 acc+= (acc>>8); | |
308 ff_count+= acc&0xFF; | |
309 } | |
310 for(; i<size; i++){ | |
311 if(buf[i]==0xFF) ff_count++; | |
312 } | |
313 | |
314 if(ff_count==0) return; | |
315 | |
316 flush_put_bits(&s->pb); | |
10327
bf04c3ecdfe5
Use skip_put_bytes in MJPEG encoder instead of filling all bytes with 0
reimar
parents:
10146
diff
changeset
|
317 skip_put_bytes(&s->pb, ff_count); |
5020 | 318 |
319 for(i=size-1; ff_count; i--){ | |
320 int v= buf[i]; | |
321 | |
322 if(v==0xFF){ | |
323 //printf("%d %d\n", i, ff_count); | |
324 buf[i+ff_count]= 0; | |
325 ff_count--; | |
326 } | |
327 | |
328 buf[i+ff_count]= v; | |
329 } | |
330 } | |
331 | |
5029
dbaa06366c3c
add a proper prefix to all mjpeg encoder exported functions
aurel
parents:
5028
diff
changeset
|
332 void ff_mjpeg_encode_stuffing(PutBitContext * pbc) |
5020 | 333 { |
334 int length; | |
335 length= (-put_bits_count(pbc))&7; | |
336 if(length) put_bits(pbc, length, (1<<length)-1); | |
337 } | |
338 | |
5029
dbaa06366c3c
add a proper prefix to all mjpeg encoder exported functions
aurel
parents:
5028
diff
changeset
|
339 void ff_mjpeg_encode_picture_trailer(MpegEncContext *s) |
5020 | 340 { |
5029
dbaa06366c3c
add a proper prefix to all mjpeg encoder exported functions
aurel
parents:
5028
diff
changeset
|
341 ff_mjpeg_encode_stuffing(&s->pb); |
5020 | 342 flush_put_bits(&s->pb); |
343 | |
344 assert((s->header_bits&7)==0); | |
345 | |
346 escape_FF(s, s->header_bits>>3); | |
347 | |
348 put_marker(&s->pb, EOI); | |
349 } | |
350 | |
5029
dbaa06366c3c
add a proper prefix to all mjpeg encoder exported functions
aurel
parents:
5028
diff
changeset
|
351 void ff_mjpeg_encode_dc(MpegEncContext *s, int val, |
dbaa06366c3c
add a proper prefix to all mjpeg encoder exported functions
aurel
parents:
5028
diff
changeset
|
352 uint8_t *huff_size, uint16_t *huff_code) |
5020 | 353 { |
354 int mant, nbits; | |
355 | |
356 if (val == 0) { | |
357 put_bits(&s->pb, huff_size[0], huff_code[0]); | |
358 } else { | |
359 mant = val; | |
360 if (val < 0) { | |
361 val = -val; | |
362 mant--; | |
363 } | |
364 | |
365 nbits= av_log2_16bit(val) + 1; | |
366 | |
367 put_bits(&s->pb, huff_size[nbits], huff_code[nbits]); | |
368 | |
7260
3ec34b551aae
bitstream: move put_sbits() from flacenc.c to bitstream.h and use it
ramiro
parents:
7136
diff
changeset
|
369 put_sbits(&s->pb, nbits, mant); |
5020 | 370 } |
371 } | |
372 | |
373 static void encode_block(MpegEncContext *s, DCTELEM *block, int n) | |
374 { | |
375 int mant, nbits, code, i, j; | |
376 int component, dc, run, last_index, val; | |
377 MJpegContext *m = s->mjpeg_ctx; | |
378 uint8_t *huff_size_ac; | |
379 uint16_t *huff_code_ac; | |
380 | |
381 /* DC coef */ | |
382 component = (n <= 3 ? 0 : (n&1) + 1); | |
383 dc = block[0]; /* overflow is impossible */ | |
384 val = dc - s->last_dc[component]; | |
385 if (n < 4) { | |
5029
dbaa06366c3c
add a proper prefix to all mjpeg encoder exported functions
aurel
parents:
5028
diff
changeset
|
386 ff_mjpeg_encode_dc(s, val, m->huff_size_dc_luminance, m->huff_code_dc_luminance); |
5020 | 387 huff_size_ac = m->huff_size_ac_luminance; |
388 huff_code_ac = m->huff_code_ac_luminance; | |
389 } else { | |
5029
dbaa06366c3c
add a proper prefix to all mjpeg encoder exported functions
aurel
parents:
5028
diff
changeset
|
390 ff_mjpeg_encode_dc(s, val, m->huff_size_dc_chrominance, m->huff_code_dc_chrominance); |
5020 | 391 huff_size_ac = m->huff_size_ac_chrominance; |
392 huff_code_ac = m->huff_code_ac_chrominance; | |
393 } | |
394 s->last_dc[component] = dc; | |
395 | |
396 /* AC coefs */ | |
397 | |
398 run = 0; | |
399 last_index = s->block_last_index[n]; | |
400 for(i=1;i<=last_index;i++) { | |
401 j = s->intra_scantable.permutated[i]; | |
402 val = block[j]; | |
403 if (val == 0) { | |
404 run++; | |
405 } else { | |
406 while (run >= 16) { | |
407 put_bits(&s->pb, huff_size_ac[0xf0], huff_code_ac[0xf0]); | |
408 run -= 16; | |
409 } | |
410 mant = val; | |
411 if (val < 0) { | |
412 val = -val; | |
413 mant--; | |
414 } | |
415 | |
416 nbits= av_log2(val) + 1; | |
417 code = (run << 4) | nbits; | |
418 | |
419 put_bits(&s->pb, huff_size_ac[code], huff_code_ac[code]); | |
420 | |
7260
3ec34b551aae
bitstream: move put_sbits() from flacenc.c to bitstream.h and use it
ramiro
parents:
7136
diff
changeset
|
421 put_sbits(&s->pb, nbits, mant); |
5020 | 422 run = 0; |
423 } | |
424 } | |
425 | |
426 /* output EOB only if not already 64 values */ | |
427 if (last_index < 63 || run != 0) | |
428 put_bits(&s->pb, huff_size_ac[0], huff_code_ac[0]); | |
429 } | |
430 | |
5029
dbaa06366c3c
add a proper prefix to all mjpeg encoder exported functions
aurel
parents:
5028
diff
changeset
|
431 void ff_mjpeg_encode_mb(MpegEncContext *s, DCTELEM block[6][64]) |
5020 | 432 { |
433 int i; | |
434 for(i=0;i<5;i++) { | |
435 encode_block(s, block[i], i); | |
436 } | |
437 if (s->chroma_format == CHROMA_420) { | |
438 encode_block(s, block[5], 5); | |
439 } else { | |
440 encode_block(s, block[6], 6); | |
441 encode_block(s, block[5], 5); | |
442 encode_block(s, block[7], 7); | |
443 } | |
444 } | |
5030
9ce0052d533a
move the mjpeg_encoder struct from mpegvideo.c to mjpegenc.c
aurel
parents:
5029
diff
changeset
|
445 |
9ce0052d533a
move the mjpeg_encoder struct from mpegvideo.c to mjpegenc.c
aurel
parents:
5029
diff
changeset
|
446 AVCodec mjpeg_encoder = { |
9ce0052d533a
move the mjpeg_encoder struct from mpegvideo.c to mjpegenc.c
aurel
parents:
5029
diff
changeset
|
447 "mjpeg", |
9ce0052d533a
move the mjpeg_encoder struct from mpegvideo.c to mjpegenc.c
aurel
parents:
5029
diff
changeset
|
448 CODEC_TYPE_VIDEO, |
9ce0052d533a
move the mjpeg_encoder struct from mpegvideo.c to mjpegenc.c
aurel
parents:
5029
diff
changeset
|
449 CODEC_ID_MJPEG, |
9ce0052d533a
move the mjpeg_encoder struct from mpegvideo.c to mjpegenc.c
aurel
parents:
5029
diff
changeset
|
450 sizeof(MpegEncContext), |
9ce0052d533a
move the mjpeg_encoder struct from mpegvideo.c to mjpegenc.c
aurel
parents:
5029
diff
changeset
|
451 MPV_encode_init, |
9ce0052d533a
move the mjpeg_encoder struct from mpegvideo.c to mjpegenc.c
aurel
parents:
5029
diff
changeset
|
452 MPV_encode_picture, |
9ce0052d533a
move the mjpeg_encoder struct from mpegvideo.c to mjpegenc.c
aurel
parents:
5029
diff
changeset
|
453 MPV_encode_end, |
10146
38cfe222e1a4
Mark all pix_fmts and supported_framerates compound literals as const.
reimar
parents:
9431
diff
changeset
|
454 .pix_fmts= (const enum PixelFormat[]){PIX_FMT_YUVJ420P, PIX_FMT_YUVJ422P, PIX_FMT_NONE}, |
7040
e943e1409077
Make AVCodec long_names definition conditional depending on CONFIG_SMALL.
stefano
parents:
6788
diff
changeset
|
455 .long_name= NULL_IF_CONFIG_SMALL("MJPEG (Motion JPEG)"), |
5030
9ce0052d533a
move the mjpeg_encoder struct from mpegvideo.c to mjpegenc.c
aurel
parents:
5029
diff
changeset
|
456 }; |