10 #define FLAG_FRAMERATE 4
11 #define FLAG_FULLRANGE 8
13 #define FLAG_ITU709 16
14 #define FLAG_SMPTE240M 32
15 #define FLAG_CS_MASK 48
17 #define FLAG_FAKENLARGE 128
18 #define FLAG_DEDUP 256
23 //Heh, this happens to be exact hardware capacity of 1.44MB 90mm floppy. :-)
24 //This buffer needs to be big enough to store 512x480 16-bit YCbCr 4:4:4 (6 bytes per pixel) image.
25 unsigned char yuv_buffer
[1474560];
26 unsigned char old_yuv_buffer
[1474560];
29 uint32_t ymatrix
[0x80000];
30 uint32_t cbmatrix
[0x80000];
31 uint32_t crmatrix
[0x80000];
34 #define TOYUV(src, idx) do {\
35 uint32_t c = (static_cast<uint32_t>(src[(idx) + 0]) << 24) |\
36 (static_cast<uint32_t>(src[(idx) + 1]) << 16) |\
37 (static_cast<uint32_t>(src[(idx) + 2]) << 8) |\
38 static_cast<uint32_t>(src[(idx) + 3]);\
39 Y += ymatrix[c & 0x7FFFF];\
40 Cb += cbmatrix[c & 0x7FFFF];\
41 Cr += crmatrix[c & 0x7FFFF];\
44 #define RGB2YUV_SHIFT 14
48 static const size_t esize
= 2;
49 static void store(unsigned char* buffer
, size_t idx
, size_t psep
, uint32_t v1
, uint32_t v2
,
52 *reinterpret_cast<uint16_t*>(buffer
+ idx
) = (v1
>> RGB2YUV_SHIFT
);
53 *reinterpret_cast<uint16_t*>(buffer
+ idx
+ psep
) = (v2
>> RGB2YUV_SHIFT
);
54 *reinterpret_cast<uint16_t*>(buffer
+ idx
+ 2 * psep
) = (v3
>> RGB2YUV_SHIFT
);
60 static const size_t esize
= 1;
61 static void store(unsigned char* buffer
, size_t idx
, size_t psep
, uint32_t v1
, uint32_t v2
,
64 buffer
[idx
] = (v1
>> (RGB2YUV_SHIFT
+ 8));
65 buffer
[idx
+ psep
] = (v2
>> (RGB2YUV_SHIFT
+ 8));
66 buffer
[idx
+ 2 * psep
] = (v3
>> (RGB2YUV_SHIFT
+ 8));
70 template<class store
, size_t ioff1
, size_t ioff2
, size_t ioff3
, size_t ooff1
, size_t ooff2
, size_t ooff3
>
73 static const size_t esize
= store::esize
;
74 static void convert(unsigned char* obuffer
, size_t oidx
, const unsigned char* ibuffer
, size_t iidx
,
77 //Compiler should be able to eliminate every if out of this.
83 TOYUV(ibuffer
, iidx
+ ioff1
* 4);
85 TOYUV(ibuffer
, iidx
+ ioff2
* 4);
87 TOYUV(ibuffer
, iidx
+ ioff3
* 4);
88 if(ioff1
> 0 && ioff2
> 0 && ioff3
> 0) {
92 } else if(ioff1
> 0) {
97 store::store(obuffer
, oidx
, psep
, Y
, Cb
, Cr
);
99 store::store(obuffer
, oidx
+ ooff1
* store::esize
, psep
, Y
, Cb
, Cr
);
101 store::store(obuffer
, oidx
+ ooff2
* store::esize
, psep
, Y
, Cb
, Cr
);
103 store::store(obuffer
, oidx
+ ooff3
* store::esize
, psep
, Y
, Cb
, Cr
);
107 template<class proc
, size_t lim
, size_t igap
, size_t ogap
>
110 static void f(unsigned char* buffer
, const unsigned char* src
, size_t psep
)
112 for(size_t i
= 0; i
< lim
; i
++)
113 proc::convert(buffer
, proc::esize
* ogap
* i
, src
, 4 * igap
* i
, psep
);
117 //Render a line pair of YUV with 256x224/240
118 template<class store
>
119 void render_yuv_256_240(unsigned char* buffer
, const unsigned char* src
, size_t psep
, bool hires
, bool interlaced
)
123 loop
<loadstore
<store
, 1, 512, 513, 0, 0, 0>, 256, 2, 1>::f(buffer
, src
, psep
);
125 loop
<loadstore
<store
, 1, 0, 0, 0, 0, 0>, 256, 2, 1>::f(buffer
, src
, psep
);
128 loop
<loadstore
<store
, 512, 0, 0, 0, 0, 0>, 256, 1, 1>::f(buffer
, src
, psep
);
130 loop
<loadstore
<store
, 0, 0, 0, 0, 0, 0>, 256, 1, 1>::f(buffer
, src
, psep
);
133 //Render a line pair of YUV with 512x224/240
134 template<class store
>
135 void render_yuv_512_240(unsigned char* buffer
, const unsigned char* src
, size_t psep
, bool hires
, bool interlaced
)
139 loop
<loadstore
<store
, 512, 0, 0, 0, 0, 0>, 512, 1, 1>::f(buffer
, src
, psep
);
141 loop
<loadstore
<store
, 0, 0, 0, 0, 0, 0>, 512, 1, 1>::f(buffer
, src
, psep
);
144 loop
<loadstore
<store
, 512, 0, 0, 1, 0, 0>, 256, 1, 2>::f(buffer
, src
, psep
);
146 loop
<loadstore
<store
, 0, 0, 0, 1, 0, 0>, 256, 1, 2>::f(buffer
, src
, psep
);
149 //Render a line pair of YUV with 256x448/480
150 template<class store
>
151 void render_yuv_256_480(unsigned char* buffer
, const unsigned char* src
, size_t psep
, bool hires
, bool interlaced
)
155 loop
<loadstore
<store
, 1, 0, 0, 0, 0, 0>, 256, 2, 1>::f(buffer
, src
, psep
);
156 loop
<loadstore
<store
, 1, 0, 0, 0, 0, 0>, 256, 2, 1>::f(buffer
+ 256 * store::esize
,
159 loop
<loadstore
<store
, 1, 0, 0, 256, 0, 0>, 256, 2, 1>::f(buffer
, src
, psep
);
162 loop
<loadstore
<store
, 0, 0, 0, 0, 0, 0>, 256, 1, 1>::f(buffer
, src
, psep
);
163 loop
<loadstore
<store
, 0, 0, 0, 0, 0, 0>, 256, 1, 1>::f(buffer
+ 256 * store::esize
,
166 loop
<loadstore
<store
, 0, 0, 0, 256, 0, 0>, 256, 1, 1>::f(buffer
, src
, psep
);
169 //Render a line pair of YUV with 512x448/480
170 template<class store
>
171 void render_yuv_512_480(unsigned char* buffer
, const unsigned char* src
, size_t psep
, bool hires
, bool interlaced
)
175 loop
<loadstore
<store
, 0, 0, 0, 0, 0, 0>, 512, 1, 1>::f(buffer
, src
, psep
);
176 loop
<loadstore
<store
, 0, 0, 0, 0, 0, 0>, 512, 1, 1>::f(buffer
+ 512 * store::esize
,
179 loop
<loadstore
<store
, 0, 0, 0, 512, 0, 0>, 512, 1, 1>::f(buffer
, src
, psep
);
182 loop
<loadstore
<store
, 0, 0, 0, 1, 0, 0>, 256, 1, 2>::f(buffer
, src
, psep
);
183 loop
<loadstore
<store
, 0, 0, 0, 1, 0, 0>, 256, 1, 2>::f(buffer
+ 512 * store::esize
,
186 loop
<loadstore
<store
, 0, 0, 0, 1, 512, 513>, 256, 1, 2>::f(buffer
, src
, psep
);
189 //Render a line pair of YUV with 512x448/480 fakeexpand
190 template<class store
>
191 void render_yuv_fe(unsigned char* buffer
, const unsigned char* src
, size_t psep
, bool hires
, bool interlaced
)
195 loop
<loadstore
<store
, 0, 0, 0, 0, 0, 0>, 512, 1, 1>::f(buffer
, src
, psep
);
196 loop
<loadstore
<store
, 0, 0, 0, 0, 0, 0>, 512, 1, 1>::f(buffer
+ 512 * store::esize
,
199 loop
<loadstore
<store
, 1, 0, 0, 1, 512, 513>, 256, 2, 2>::f(buffer
, src
, psep
);
202 loop
<loadstore
<store
, 0, 0, 0, 1, 0, 0>, 256, 1, 2>::f(buffer
, src
, psep
);
203 loop
<loadstore
<store
, 0, 0, 0, 1, 0, 0>, 256, 1, 2>::f(buffer
+ 512 * store::esize
,
206 loop
<loadstore
<store
, 0, 0, 0, 1, 256, 257>, 256, 1, 2>::f(buffer
, src
, psep
);
209 void init_matrix(double Kb
, double Kr
, bool fullrange
)
212 double GY
= 1 - Kr
- Kb
;
214 double RPb
= -0.5 * Kr
/ (1 - Kb
);
215 double GPb
= -0.5 * (1 - Kr
- Kb
) / (1 - Kb
);
218 double GPr
= -0.5 * (1 - Kr
- Kb
) / (1 - Kr
);
219 double BPr
= -0.5 * Kb
/ (1 - Kr
);
220 for(uint32_t i
= 0; i
< 0x80000; i
++) {
221 uint32_t l
= 1 + ((i
>> 15) & 0xF);
222 //Range of (r,g,b) is 0...496.
223 uint32_t r
= (l
* ((i
>> 0) & 0x1F));
224 uint32_t g
= (l
* ((i
>> 5) & 0x1F));
225 uint32_t b
= (l
* ((i
>> 10) & 0x1F));
226 double Y
= (RY
* r
+ GY
* g
+ BY
* b
) / 496 * (fullrange
? 255 : 219) + (fullrange
? 0 : 16);
227 double Cb
= (RPb
* r
+ GPb
* g
+ BPb
* b
) / 496 * (fullrange
? 255 : 224) + 128;
228 double Cr
= (RPr
* r
+ GPr
* g
+ BPr
* b
) / 496 * (fullrange
? 255 : 224) + 128;
229 ymatrix
[i
] = static_cast<uint32_t>(Y
* 4194304 + 0.5);
230 cbmatrix
[i
] = static_cast<uint32_t>(Cb
* 4194304 + 0.5);
231 crmatrix
[i
] = static_cast<uint32_t>(Cr
* 4194304 + 0.5);
235 //Load RGB to YUV conversion matrix.
236 void load_rgb2yuv_matrix(uint32_t flags
)
238 switch(flags
& (FLAG_CS_MASK
| FLAG_FULLRANGE
))
241 init_matrix(0.114, 0.229, false);
243 case FLAG_ITU601
| FLAG_FULLRANGE
:
244 init_matrix(0.114, 0.229, true);
247 init_matrix(0.0722, 0.2126, false);
249 case FLAG_ITU709
| FLAG_FULLRANGE
:
250 init_matrix(0.0722, 0.2126, true);
253 init_matrix(0.087, 0.212, false);
255 case FLAG_SMPTE240M
| FLAG_FULLRANGE
:
256 init_matrix(0.087, 0.212, true);
259 init_matrix(0.114, 0.229, false);
264 uint64_t double_to_ieeefp(double v
)
277 for(unsigned i
= 0; i
< 52; i
++) {
279 v2
= 2 * v2
+ ((v
>= 1) ? 1 : 0);
286 void sdump2sox(std::istream
& in
, std::ostream
& yout
, std::ostream
& sout
, std::ostream
& tout
, int32_t flags
)
291 if(flags
& FLAG_DEDUP
)
292 tout
<< "# timecode format v2" << std::endl
;
293 void (*render_yuv
)(unsigned char* buffer
, const unsigned char* src
, size_t psep
, bool hires
, bool interlaced
);
294 switch(flags
& (FLAG_WIDTH
| FLAG_HEIGHT
| FLAG_8BIT
| FLAG_FAKENLARGE
)) {
296 render_yuv
= render_yuv_256_240
<store16
>;
299 render_yuv
= render_yuv_512_240
<store16
>;
302 render_yuv
= render_yuv_256_480
<store16
>;
304 case FLAG_WIDTH
| FLAG_HEIGHT
:
305 render_yuv
= render_yuv_512_480
<store16
>;
307 case FLAG_WIDTH
| FLAG_HEIGHT
| FLAG_FAKENLARGE
:
308 render_yuv
= render_yuv_fe
<store16
>;
311 render_yuv
= render_yuv_256_240
<store8
>;
313 case FLAG_WIDTH
| FLAG_8BIT
:
314 render_yuv
= render_yuv_512_240
<store8
>;
316 case FLAG_HEIGHT
| FLAG_8BIT
:
317 render_yuv
= render_yuv_256_480
<store8
>;
319 case FLAG_WIDTH
| FLAG_HEIGHT
| FLAG_8BIT
:
320 render_yuv
= render_yuv_512_480
<store8
>;
322 case FLAG_WIDTH
| FLAG_HEIGHT
| FLAG_FAKENLARGE
| FLAG_8BIT
:
323 render_yuv
= render_yuv_fe
<store8
>;
326 unsigned char header
[12];
327 in
.read(reinterpret_cast<char*>(header
), 12);
329 throw std::runtime_error("Can't read sdump header");
330 if(header
[0] != 'S' || header
[1] != 'D' || header
[2] != 'M' || header
[3] != 'P')
331 throw std::runtime_error("Bad sdump magic");
334 cpurate
= (static_cast<uint32_t>(header
[4]) << 24) |
335 (static_cast<uint32_t>(header
[5]) << 16) |
336 (static_cast<uint32_t>(header
[6]) << 8) |
337 static_cast<uint32_t>(header
[7]);
338 apurate
= (static_cast<uint32_t>(header
[8]) << 24) |
339 (static_cast<uint32_t>(header
[9]) << 16) |
340 (static_cast<uint32_t>(header
[10]) << 8) |
341 static_cast<uint32_t>(header
[11]);
342 uint64_t sndrateR
= double_to_ieeefp(static_cast<double>(apurate
) / 768.0);
343 unsigned char sox_header
[32] = {0};
344 sox_header
[0] = 0x2E; //Magic
345 sox_header
[1] = 0x53; //Magic
346 sox_header
[2] = 0x6F; //Magic
347 sox_header
[3] = 0x58; //Magic
348 sox_header
[4] = 0x1C; //Magic
349 sox_header
[16] = sndrateR
;
350 sox_header
[17] = sndrateR
>> 8;
351 sox_header
[18] = sndrateR
>> 16;
352 sox_header
[19] = sndrateR
>> 24;
353 sox_header
[20] = sndrateR
>> 32;
354 sox_header
[21] = sndrateR
>> 40;
355 sox_header
[22] = sndrateR
>> 48;
356 sox_header
[23] = sndrateR
>> 56;
358 sout
.write(reinterpret_cast<char*>(sox_header
), 32);
360 throw std::runtime_error("Can't write audio header");
361 uint64_t samples
= 0;
363 unsigned wrongrate
= 0;
365 load_rgb2yuv_matrix(flags
);
371 break; //End of stream.
372 if((cmd
& 0xF0) == 0) {
373 //Pictrue. Read the 1MiB of picture data one line pair at a time.
374 unsigned char buf
[4096];
375 unsigned physline
= 0;
376 bool hires
= (cmd
& 1);
377 bool interlaced
= (cmd
& 2);
378 bool overscan
= (cmd
& 4);
379 bool pal
= (cmd
& 8);
380 bool ohires
= (flags
& FLAG_WIDTH
);
381 bool ointerlaced
= (flags
& FLAG_HEIGHT
);
382 bool bits8
= (flags
& FLAG_8BIT
);
383 size_t psep
= (ohires
? 512 : 256) * (ointerlaced
? 2 : 1) * (pal
? 240 : 224) *
385 size_t lsep
= (ohires
? 512 : 256) * (ointerlaced
? 2 : 1) * (bits8
? 1 : 2);
386 for(unsigned i
= 0; i
< 256; i
++) {
387 in
.read(reinterpret_cast<char*>(buf
), 4096);
389 throw std::runtime_error("Can't read picture payload");
390 is_pal
= is_pal
|| pal
;
391 if(overscan
&& i
< 9)
393 if(!overscan
&& i
< 1)
395 if(pal
& physline
>= 239)
397 if(!pal
& physline
>= 224)
399 render_yuv(yuv_buffer
+ physline
* lsep
, buf
, psep
, hires
, interlaced
);
403 //Render a black line to pad the image.
404 memset(buf
, 0, 4096);
405 render_yuv(yuv_buffer
+ 239 * lsep
, buf
, psep
, hires
, interlaced
);
407 size_t yuvsize
= 3 * psep
;
409 //If FLAG_DEDUP is set, no frames are added or dropped to match timecodes.
410 if((flags
& (FLAG_FRAMERATE
| FLAG_DEDUP
)) == 0 && !is_pal
&& interlaced
) {
411 //This uses 357368 TU instead of 357366 TU.
412 //-> Every 178683rd frame is duplicated.
413 if(wrongrate
== 178682) {
419 if((flags
& (FLAG_FRAMERATE
| FLAG_DEDUP
)) == FLAG_FRAMERATE
&& !is_pal
&& !interlaced
) {
420 //This uses 357366 TU instead of 357368 TU.
421 //-> Every 178684th frame is dropped.
422 if(wrongrate
== 178683) {
428 if(flags
& FLAG_DEDUP
) {
429 if(memcmp(old_yuv_buffer
, yuv_buffer
, yuvsize
)) {
430 memcpy(old_yuv_buffer
, yuv_buffer
, yuvsize
);
433 elided
= (++elided
) % MAX_DEDUP
;
437 tout
<< ftcw
<< std::endl
;
439 for(unsigned k
= 0; k
< times
; k
++)
440 yout
.write(reinterpret_cast<char*>(yuv_buffer
), yuvsize
);
442 throw std::runtime_error("Can't write frame");
445 uint64_t tcc
= is_pal
? 425568000 : (interlaced
? 357368000 : 357366000);
446 ftcw
= ftcw
+ tcc
/ cpurate
;
447 ftcn
= ftcn
+ tcc
% cpurate
;
448 if(ftcn
>= cpurate
) {
452 } else if(cmd
== 16) {
453 //Sound packet. Interesting.
454 unsigned char ibuf
[4];
455 unsigned char obuf
[8];
456 in
.read(reinterpret_cast<char*>(ibuf
), 4);
458 throw std::runtime_error("Can't read sound packet payload");
467 sout
.write(reinterpret_cast<char*>(obuf
), 8);
469 throw std::runtime_error("Can't write audio sample");
472 std::ostringstream str
;
473 str
<< "Unknown command byte " << static_cast<unsigned>(cmd
);
474 throw std::runtime_error(str
.str());
476 if(lf
&& frames
% 100 == 0) {
477 std::cout
<< "\e[1G" << frames
<< " frames, " << samples
<< " samples." << std::flush
;
480 //Sox internally multiplies sample count by channel count.
481 sox_header
[8] = samples
<< 1;
482 sox_header
[9] = samples
>> 7;
483 sox_header
[10] = samples
>> 15;
484 sox_header
[11] = samples
>> 23;
485 sox_header
[12] = samples
>> 31;
486 sox_header
[13] = samples
>> 39;
487 sox_header
[14] = samples
>> 47;
488 sox_header
[15] = samples
>> 55;
489 sout
.seekp(0, std::ios::beg
);
491 throw std::runtime_error("Can't seek to fix .sox header");
492 sout
.write(reinterpret_cast<char*>(sox_header
), 32);
494 throw std::runtime_error("Can't fix audio header");
495 std::cout
<< "Sound sampling rate is " << static_cast<double>(apurate
) / 768.0 << "Hz" << std::endl
;
496 std::cout
<< "Wrote " << samples
<< " samples." << std::endl
;
497 std::cout
<< "Audio length is " << 768.0 * samples
/ apurate
<< "s." << std::endl
;
502 else if(flags
& FLAG_FRAMERATE
)
506 vrate
= cpurate
/ vrate2
;
507 std::cout
<< "Video frame rate is " << cpurate
<< "/" << vrate2
<< "Hz" << std::endl
;
508 std::cout
<< "Wrote " << frames
<< " frames." << std::endl
;
509 std::cout
<< "Video length is " << frames
/ vrate
<< "s." << std::endl
;
514 std::cerr
<< "Syntax: sdump2sox [<options>] <input-file> <yuv-output-file> <sox-output-file> "
515 << "[<tc-output-file>]" << std::endl
;
516 std::cerr
<< "-W\tDump 512-wide instead of 256-wide." << std::endl
;
517 std::cerr
<< "-H\tDump 448/480-high instead of 224/240-high." << std::endl
;
518 std::cerr
<< "-D\tDedup the output (also uses exact timecodes)." << std::endl
;
519 std::cerr
<< "-h\tDump 512x448/480, doing blending for 512x224/240." << std::endl
;
520 std::cerr
<< "-F\tDump at interlaced framerate instead of non-interlaced (no effect if dedup)." << std::endl
;
521 std::cerr
<< "-f\tDump using full range instead of TV range." << std::endl
;
522 std::cerr
<< "-7\tDump using ITU.709 instead of ITU.601." << std::endl
;
523 std::cerr
<< "-2\tDump using SMPTE-240M instead of ITU.601." << std::endl
;
524 std::cerr
<< "-8\tDump using 8 bits instead of 16 bits." << std::endl
;
527 int main(int argc
, char** argv
)
538 for(unsigned i
= 1; i
< argc
; i
++) {
539 if(argv
[i
][0] == '-')
540 for(unsigned j
= 1; argv
[i
][j
]; j
++)
546 flags
|= FLAG_HEIGHT
;
549 flags
|= FLAG_FRAMERATE
;
555 flags
|= FLAG_FULLRANGE
;
558 flags
|= (FLAG_FAKENLARGE
| FLAG_WIDTH
| FLAG_HEIGHT
);
561 if(flags
& FLAG_CS_MASK
) {
565 flags
|= FLAG_ITU709
;
568 if(flags
& FLAG_CS_MASK
) {
572 flags
|= FLAG_SMPTE240M
;
594 if(idx4
&& !(flags
& FLAG_DEDUP
)) {
598 std::ifstream
in(argv
[idx1
], std::ios::in
| std::ios::binary
);
600 std::cerr
<< "Error: Can't open '" << argv
[idx1
] << "'" << std::endl
;
603 std::ofstream
yout(argv
[idx2
], std::ios::out
| std::ios::binary
);
605 std::cerr
<< "Error: Can't open '" << argv
[idx2
] << "'" << std::endl
;
608 std::ofstream
sout(argv
[idx3
], std::ios::out
| std::ios::binary
);
610 std::cerr
<< "Error: Can't open '" << argv
[idx3
] << "'" << std::endl
;
614 if(flags
& FLAG_DEDUP
) {
616 tout
.open(argv
[idx4
], std::ios::out
);
618 tout
.open(argv
[idx2
] + std::string(".tc"), std::ios::out
);
620 std::cerr
<< "Error: Can't open '" << argv
[idx2
] << ".tc'" << std::endl
;
625 sdump2sox(in
, yout
, sout
, tout
, flags
);
630 } catch(std::exception
& e
) {
631 std::cerr
<< "Error: " << e
.what() << std::endl
;