0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050 function im = ReadQTMovie(cmd, arg)
0051 global movieStatus;
0052
0053 if nargin < 1
0054 ReadQTMovie('help')
0055 return;
0056 end
0057
0058 if exist('movieStatus') ~= 1 | isempty(movieStatus)
0059
0060 movieStatus = struct('sound_stco', [], ...
0061 'sound_stsc', [], ...
0062 'sound_stsz', [], ...
0063 'sound_stts', [], ...
0064 'video_stco', [], ...
0065 'video_stsc', [], ...
0066 'video_stsz', [], ...
0067 'video_stts', [], ...
0068 'frame_pos', [], ...
0069 'frame_desc', [], ...
0070 'handler', 'none', ...
0071 'timeScale', [], ...
0072 'name', [], ...
0073 'videoDescription', [], ...
0074 'soundDescription', [], ...
0075 'fp', -1 ...
0076 );
0077 end
0078
0079 switch lower(cmd)
0080 case 'help'
0081 fprintf('Syntax: im = ReadQTMovie(cmd, arg)\n');
0082 fprintf('The following commands are supported:\n');
0083 fprintf(' start filename - Open the indicated file and parse\n');
0084 fprintf(' its contents. Must be called first.\n');
0085 fprintf(' getframe num - Return the image indicated by this\n');
0086 fprintf(' frame number \n');
0087 fprintf(' getsound - Get all the movie''s sound.\n');
0088 fprintf(' getsamplerate - Get the sound''s sample rate.\n');
0089 fprintf(' describe - Show debugging information.\n');
0090 fprintf(' close - Close this movie.\n');
0091
0092 case 'close'
0093 if movieStatus.fp >= 0
0094 try
0095 fclose(movieStatus.fp),
0096 catch
0097 end
0098 end
0099 movieStatus.fp = -1;
0100 movieStatus = [];
0101
0102 case {'start','name'}
0103 if movieStatus.fp >= 0
0104 try
0105 fclose(movieStatus.fp),
0106 catch
0107 end
0108 end
0109
0110 name = arg;
0111 movieStatus.name = name;
0112 fp = fopen(name, 'rb', 'b');
0113 if fp < 0
0114 error('Can not open file name.');
0115 end
0116 movieStatus.fp = fp;
0117 movieStatus.sound_stco = [];
0118 movieStatus.sound_stsc = [];
0119 movieStatus.sound_stsz = [];
0120 movieStatus.sound_stts = [];
0121 movieStatus.video_stco = [];
0122 movieStatus.video_stsc = [];
0123 movieStatus.video_stsz = [];
0124 movieStatus.video_stts = [];
0125 movieStatus.videoDescription = [];
0126 movieStatus.soundDescription = [];
0127 movieStatus.frame_pos = [];
0128 movieStatus.frame_desc = [];
0129
0130 while ParseAtom(fp) > 0
0131 ;
0132 end
0133 FindVideoFrames;
0134 im = movieStatus;
0135 case {'findframe','getframe'}
0136 if nargin < 2
0137 error('Missing frame number in ReadQTMovie(''findframe'',#)')'
0138 end
0139 num = arg;
0140
0141 if num < 1 | num > length(movieStatus.frame_pos)
0142 im = [];
0143 return;
0144 end
0145
0146 desc_index = movieStatus.frame_desc(num)+1;
0147 if length(movieStatus.videoDescription) > 1
0148 vs = movieStatus.videoDescription(desc_index);
0149 else
0150 vs = movieStatus.videoDescription;
0151 end
0152 switch vs.type
0153 case 'jpeg',
0154 ;
0155
0156
0157 otherwise,
0158 error(['Can not decode a ' vs.type ' field of video.']);
0159 end
0160
0161 im = GrabFrameFromMovie(movieStatus.fp, movieStatus.frame_pos(num), ...
0162 movieStatus.video_stsz(num));
0163
0164
0165
0166
0167 case 'getcompressedframe',
0168 if nargin < 2
0169 error('Missing frame number in ReadQTMovie(''findframe'',#)')'
0170 end
0171 num = arg;
0172
0173 if num < 1 | num > length(movieStatus.frame_pos)
0174 im = [];
0175 return;
0176 end
0177
0178 desc_index = movieStatus.frame_desc(num)+1;
0179 if length(movieStatus.videoDescription) > 1
0180 vs = movieStatus.videoDescription(desc_index);
0181 else
0182 vs = movieStatus.videoDescription;
0183 end
0184
0185 fseek(movieStatus.fp, movieStatus.frame_pos(num), 'bof');
0186 im = fread(movieStatus.fp, movieStatus.video_stsz(num), 'uchar');
0187
0188 case 'getsound',
0189 if nargin < 2
0190 im = FindSound;
0191 else
0192 im = FindSound(arg(1), arg(2));
0193 end
0194 case 'getsoundsr',
0195 im = [];
0196 if HaveSoundTrack
0197 for i=1:length(movieStatus.soundDescription)
0198 sr = movieStatus.soundDescription(i).sample_rate;
0199 if ~isempty(sr)
0200 im = sr;
0201 break
0202 end
0203 end
0204 end
0205 case {'info','describe'},
0206 fprintf('Movie information for %s:\n', movieStatus.name);
0207 fprintf('\tTime Scale: %d ticks per second\n', movieStatus.timeScale);
0208 fprintf('\tVideo information:\n');
0209 fprintf('\t\tNumber of frames: %d\n', length(movieStatus.video_stsz));
0210 fprintf('\t\tNumber of chunk offset atoms (stco): %d\n', ...
0211 length(movieStatus.video_stco));
0212 fprintf('\t\tNumber of sample to chunk atoms (stsc): %d\n', ...
0213 length(movieStatus.video_stsc));
0214 for i=1:min(4,size(movieStatus.video_stsc,2));
0215 fprintf('\t\t\tfirst sample %d, %d samples/chunk, type %d\n',...
0216 movieStatus.video_stsc(1,i), ...
0217 movieStatus.video_stsc(2,i), ...
0218 movieStatus.video_stsc(3,i));
0219 end
0220 fprintf('\t\tNumber of sample size atoms (stsz): %d\n', ...
0221 length(movieStatus.video_stsz));
0222 fprintf('\t\t\t');
0223 for i=1:min(8,length(movieStatus.video_stsz))
0224 if i > 1
0225 fprintf(', ');
0226 end
0227 fprintf('%d', movieStatus.video_stsz(i));
0228 end
0229 fprintf('\n');
0230 fprintf('\t\tNumber of sample to time atoms (stts): %d\n', ...
0231 length(movieStatus.video_stts));
0232 for i=1:min(3,size(movieStatus.video_stts,2))
0233 fprintf('\t\t\t%d frames separated by %d ticks', ...
0234 movieStatus.video_stts(1,i), ...
0235 movieStatus.video_stts(2,i));
0236 fprintf(' (%g ms)\n', ...
0237 movieStatus.video_stts(2,i)/movieStatus.timeScale*1000);
0238 end
0239 for i=1:length(movieStatus.videoDescription)
0240 if ~isempty(movieStatus.videoDescription(i).type)
0241 fprintf('\t\tTrack %d Format: %s\n', i-1, ...
0242 movieStatus.videoDescription(i).type);
0243 fprintf('\t\t\t%d x %d, tqual is %d, squal is %d.\n',...
0244 movieStatus.videoDescription(i).width, ...
0245 movieStatus.videoDescription(i).height, ...
0246 movieStatus.videoDescription(i).tqual, ...
0247 movieStatus.videoDescription(i).squal);
0248 end
0249 end
0250
0251 fprintf('\tSound information:\n');
0252 fprintf('\t\tNumber of chunk offset atoms (stco): %d\n', ...
0253 length(movieStatus.sound_stco));
0254 fprintf('\t\tNumber of sample to chunk atoms (stsc): %d\n', ...
0255 length(movieStatus.sound_stsc));
0256 for i=1:min(4,size(movieStatus.sound_stsc,2));
0257 fprintf('\t\t\tfirst sample %d, %d samples/chunk, type %d\n',...
0258 movieStatus.sound_stsc(1,i), ...
0259 movieStatus.sound_stsc(2,i), ...
0260 movieStatus.sound_stsc(3,i));
0261 end
0262 fprintf('\t\tNumber of sample size atoms (stsz): %d\n', ...
0263 length(movieStatus.sound_stsz));
0264 fprintf('\t\t\t');
0265 for i=1:min(8,length(movieStatus.sound_stsz))
0266 if i > 1
0267 fprintf(', ');
0268 end
0269 fprintf('%d', movieStatus.sound_stsz(i));
0270 end
0271 fprintf('\n');
0272 fprintf('\t\tNumber of sample to time atoms (stts): %d\n', ...
0273 length(movieStatus.sound_stts));
0274 for i=1:min(3,size(movieStatus.sound_stts,2))
0275 fprintf('\t\t\t%d frames separated by %d ticks', ...
0276 movieStatus.sound_stts(1,i), ...
0277 movieStatus.sound_stts(2,i));
0278 fprintf(' (%g ms)\n', ...
0279 movieStatus.sound_stts(2,i)/movieStatus.timeScale*1000);
0280
0281 end
0282 for i=1:length(movieStatus.soundDescription)
0283 if ~isempty(movieStatus.soundDescription(i).type)
0284 fprintf('\t\tTrack %d Format: %s\n', i-1, ...
0285 movieStatus.soundDescription(i).type);
0286 fprintf('\t\t\t%d channel, %d bits/sample, %d Hz\n',...
0287 movieStatus.soundDescription(i).channels, ...
0288 movieStatus.soundDescription(i).bits, ...
0289 movieStatus.soundDescription(i).sample_rate);
0290
0291 end
0292 end
0293
0294 otherwise
0295 error('Unknown command to ReadQTMovie')
0296 end
0297
0298
0299
0300
0301
0302
0303
0304
0305 function FindVideoFrames
0306 global movieStatus
0307 pos = [];
0308 description_index = [];
0309 current_stsc_index = 1;
0310 current_frame = 1;
0311
0312
0313
0314 for c=1:length(movieStatus.video_stco)
0315
0316
0317 chunk_pos = movieStatus.video_stco(c);
0318
0319
0320
0321
0322
0323 if current_stsc_index < size(movieStatus.video_stsc,2)
0324 if c >= movieStatus.video_stsc(1,current_stsc_index+1)
0325 current_stsc_index = current_stsc_index+1;
0326 end
0327 end
0328
0329
0330
0331 frames_per_chunk = movieStatus.video_stsc(2,current_stsc_index);
0332
0333
0334 sizes = movieStatus.video_stsz(current_frame: ...
0335 (current_frame+frames_per_chunk-1))';
0336
0337
0338
0339
0340
0341
0342
0343 if frames_per_chunk > 1
0344 pos = [pos chunk_pos chunk_pos+cumsum(sizes(1:end-1))];
0345 else
0346 pos = [pos chunk_pos];
0347 end
0348
0349
0350
0351
0352 current_frame = current_frame + frames_per_chunk;
0353
0354
0355
0356 description_index = [description_index ones(1,length(sizes)) * ...
0357 movieStatus.video_stsc(3,current_stsc_index)];
0358 end
0359 movieStatus.frame_pos = pos;
0360 movieStatus.frame_desc = description_index;
0361
0362
0363 function sound = FindSound(firstSample, lastSample)
0364 global movieStatus
0365 current_stsc_index = 1;
0366 totalLength = 0;
0367 chunk_pos = zeros(1,length(movieStatus.sound_stco));
0368 chunk_desc = chunk_pos;
0369 chunk_frames = chunk_pos;
0370
0371
0372 if length(movieStatus.sound_stsz) > 1
0373 warning('Got an stsz atom I don''t know what to do with.\n');
0374 end
0375 sizes = movieStatus.sound_stsz(1);
0376
0377
0378
0379
0380 for c=1:length(movieStatus.sound_stco)
0381
0382
0383 chunk_pos(c) = movieStatus.sound_stco(c);
0384
0385
0386
0387
0388
0389 if current_stsc_index < size(movieStatus.sound_stsc,2)
0390 if c >= movieStatus.sound_stsc(1,current_stsc_index+1)
0391 current_stsc_index = current_stsc_index+1;
0392 end
0393 end
0394
0395
0396
0397 chunk_frames(c) = movieStatus.sound_stsc(2,current_stsc_index);
0398
0399 chunk_desc(c) = movieStatus.sound_stsc(3,current_stsc_index)+1;
0400
0401
0402
0403
0404
0405
0406
0407
0408
0409 if (chunk_desc(c) > length(movieStatus.soundDescription))
0410 chunk_desc(c) = length(movieStatus.soundDescription);
0411 disp('Adjusting sound description id in FindSound.');
0412 end
0413
0414 ch = movieStatus.soundDescription(chunk_desc(c)).channels;
0415 if c == 1
0416 channels = ch;
0417 elseif channels ~= ch
0418 er = fprintf('Channel count changed %d to %d at chunk %d.\n',...
0419 channels, ch, c);
0420 error(er);
0421 end
0422 totalLength = totalLength + sizes*chunk_frames(c);
0423 end
0424
0425 if nargin < 1
0426 firstSample = 0;
0427 end
0428 if nargin < 2
0429 lastSample = totalLength;
0430 end
0431 fprintf('Getting sound from sample %d to %d from %d total samples.\n', ...
0432 firstSample, lastSample, totalLength);
0433
0434 sound = zeros(lastSample-firstSample+1, channels);
0435 inputPos = 1;
0436 outputPos = 1;
0437 for c=1:length(chunk_desc)
0438 desc = chunk_desc(c);
0439 inputCnt = sizes * chunk_frames(c);
0440 if inputPos+inputCnt-1 >= firstSample & inputPos <= lastSample
0441
0442 skip = max(0, firstSample - inputPos);
0443 outputCnt = min(inputCnt-skip, size(sound,1) - outputPos + 1);
0444
0445
0446
0447
0448 fseek(movieStatus.fp, chunk_pos(c), 'bof');
0449 switch movieStatus.soundDescription(desc).type
0450 case 'raw ',
0451 chunk = fread(movieStatus.fp,inputCnt*channels,'uchar');
0452 chunk = (chunk-128)/128;
0453 case 'twos',
0454 chunk = fread(movieStatus.fp,inputCnt*channels,'int16');
0455 chunk = chunk/32768;
0456 otherwise,
0457 error('Unknown sound format');
0458 end
0459 chunk = reshape(chunk, channels, inputCnt)';
0460 chunkEnd = outputPos+outputCnt-1;
0461 if chunkEnd > size(sound,1)
0462 error('Internal Error: chunkEnd got too big.');
0463 end
0464 sound(outputPos:chunkEnd,:) = chunk(skip+1:skip+outputCnt,:);
0465 outputPos = outputPos + outputCnt;
0466 end
0467 inputPos = inputPos + inputCnt;
0468 end
0469
0470
0471
0472 function im = GrabFrameFromMovie(fp, pos, framelen)
0473 global movieStatus
0474
0475 imageTmp = tempname;
0476 tempfp = fopen(imageTmp, 'wb');
0477 if tempfp < 0
0478 error ('Could not open temporary file for grabbing QT movie frame.');
0479 end
0480
0481 fseek(fp, pos, 'bof');
0482 lensofar = 0;
0483 while lensofar < framelen
0484 data = fread(fp, min(1024*16, framelen-lensofar), 'uchar');
0485 if isempty(data)
0486 break;
0487 end
0488 cnt = fwrite(tempfp, data, 'uchar');
0489 lensofar = lensofar + cnt;
0490 end
0491 fclose(tempfp);
0492 im = imread(imageTmp);
0493 delete(imageTmp);
0494
0495
0496 function res = HaveVideoTrack()
0497 global movieStatus
0498 res = ~isempty(movieStatus.video_stco) & ~isempty(movieStatus.video_stsc) & ...
0499 ~isempty(movieStatus.video_stsz);
0500
0501
0502 function res = HaveSoundTrack()
0503 global movieStatus
0504 res = ~isempty(movieStatus.sound_stco) & ~isempty(movieStatus.sound_stsc) & ...
0505 ~isempty(movieStatus.sound_stsz);
0506
0507
0508
0509
0510
0511
0512 function DuplicateTrack(mode, atom)
0513 fprintf('Found duplicate %s track in QuickTime movie (%s atom).\n',mode,atom);
0514 error('Unable to process duplicate tracks.');
0515
0516
0517
0518 function size = ParseAtom(fp)
0519 global movieStatus
0520 size = Read32Bits(fp);
0521 type = Read4ByteString(fp);
0522 place = 8;
0523
0524 if isempty(size)
0525 size = 0;
0526 return;
0527 end
0528
0529
0530 switch type
0531 case 'dref',
0532 Read32Bits(fp, 2);
0533 place = place + 8;
0534 while place < size
0535 place = place + ParseAtom(fp);
0536 end
0537 case {'edts','mdia','minf','moov','stbl'},
0538 while place < size
0539 place = place + ParseAtom(fp);
0540 end
0541 case 'trak',
0542 while place < size
0543 place = place + ParseAtom(fp);
0544 end
0545
0546
0547
0548 case 'hdlr',
0549 Read32Bits(fp);
0550 type = Read4ByteString(fp);
0551 sub = Read4ByteString(fp);
0552 place = place + 12;
0553
0554 if strcmp(type, 'mhlr')
0555 movieStatus.handler = sub;
0556 end
0557 case 'mvhd',
0558 Read32Bits(fp, 3);
0559 movieStatus.timeScale = Read32Bits(fp);
0560 place = place + 16;
0561
0562
0563 case 'stco',
0564 Read32Bits(fp);
0565 count = Read32Bits(fp);
0566 place = place + 8;
0567
0568
0569 switch movieStatus.handler
0570 case 'soun',
0571 if isempty(movieStatus.sound_stco)
0572 movieStatus.sound_stco = Read32Bits(fp, count);
0573 else
0574 DuplicateTrack('sound','stco');
0575 end
0576 case 'vide',
0577 if isempty(movieStatus.video_stco)
0578 movieStatus.video_stco = Read32Bits(fp, count);
0579 else
0580 DuplicateTrack('video','stco');
0581 end
0582 end
0583 place = place + 4 * count;
0584
0585
0586
0587
0588
0589
0590
0591
0592 case 'stsc',
0593 Read32Bits(fp);
0594 count = Read32Bits(fp);
0595 place = place + 8;
0596
0597
0598 data = Read32Bits(fp, count*3);
0599 switch movieStatus.handler
0600 case 'soun',
0601 if isempty(movieStatus.sound_stsc)
0602 movieStatus.sound_stsc = reshape(data,3,count);
0603 else
0604 DuplicateTrack('sound','stsc');
0605 end
0606 case 'vide',
0607 if isempty(movieStatus.video_stsc)
0608 movieStatus.video_stsc = reshape(data,3,count);
0609 else
0610 DuplicateTrack('video','stsc');
0611 end
0612 end
0613 place = place + 4 * 3 * count;
0614
0615
0616 case 'stsd',
0617 Read32Bits(fp);
0618 count = Read32Bits(fp);
0619 place = place + 8;
0620 for i=1:count
0621 place = place + ParseSampleDescription(fp);
0622 end
0623 case 'stts',
0624 Read32Bits(fp);
0625 count = Read32Bits(fp);
0626 place = place + 8;
0627 data = Read32Bits(fp, count*2);
0628 place = place + 2*count*4;
0629 switch movieStatus.handler
0630 case 'soun',
0631 if isempty(movieStatus.sound_stts)
0632 movieStatus.sound_stts = reshape(data,2,count);
0633 else
0634 DuplicateTrack('sound','stts');
0635 end
0636 case 'vide',
0637 if isempty(movieStatus.video_stts)
0638 movieStatus.video_stts = reshape(data,2,count);
0639 else
0640 DuplicateTrack('video','stts');
0641 end
0642 end
0643
0644
0645
0646
0647
0648 case 'stsz',
0649 flags = Read32Bits(fp);
0650 sizedata = Read32Bits(fp);
0651 count = Read32Bits(fp);
0652 place = place + 12;
0653 if sizedata > 0
0654 data = sizedata;
0655 else
0656
0657
0658 data = Read32Bits(fp, count);
0659 place = place + 4 * count;
0660 end
0661 switch movieStatus.handler
0662 case 'soun',
0663 if isempty(movieStatus.sound_stsz)
0664 movieStatus.sound_stsz = data;
0665 else
0666 DuplicateTrack('sound','stsz');
0667 end
0668 case 'vide',
0669 if isempty(movieStatus.video_stsz)
0670 movieStatus.video_stsz = data;
0671 else
0672 DuplicateTrack('video','stsz');
0673 end
0674 end
0675 case {'dinf','elst','mdat','raw ','rpza', 'jpeg', 'rle ','smhd', ...
0676 'stgs', 'stss', 'tkhd', 'vmhd'}
0677
0678 otherwise,
0679
0680 end
0681 if size-place < 0
0682 error('got out of sync while reading QT movie.');
0683 end
0684 fseek(fp, size-place, 'cof');
0685
0686
0687
0688
0689
0690
0691
0692 function size = ParseSampleDescription(fp)
0693 global movieStatus
0694 size = Read32Bits(fp);
0695 type = Read4ByteString(fp);
0696 place = 8;
0697
0698 if isempty(size)
0699 size = 0;
0700 return;
0701 end
0702
0703 Read16Bits(fp, 3);
0704 reference_index = Read16Bits(fp)+1;
0705 place = place + 8;
0706
0707 switch movieStatus.handler
0708 case 'soun',
0709 Read32Bits(fp);
0710 Read32Bits(fp);
0711 num_channels = Read16Bits(fp);
0712 num_bits = Read16Bits(fp);
0713 compress_packet = Read32Bits(fp);
0714 sample_rate = Read32Bits(fp)/65536.0;
0715 place = place + 20;
0716
0717 if(isempty(movieStatus.soundDescription))
0718 movieStatus.soundDescription = ...
0719 repmat(struct('type',[],'channels',[],'bits',[],'sample_rate',[]),1,1);
0720 else
0721 movieStatus.soundDescription(end+1)=movieStatus.soundDescription(end);
0722 end
0723 movieStatus.soundDescription(reference_index).type = type;
0724 movieStatus.soundDescription(reference_index).channels = num_channels;
0725 movieStatus.soundDescription(reference_index).bits = num_bits;
0726 movieStatus.soundDescription(reference_index).sample_rate = sample_rate;
0727
0728
0729
0730
0731
0732 case 'vide',
0733 Read32Bits(fp);
0734 Read32Bits(fp);
0735 tqual = Read32Bits(fp);
0736 squal = Read32Bits(fp);
0737 width = Read16Bits(fp);
0738 height = Read16Bits(fp);
0739 hres = Read16Bits(fp);
0740 vres = Read16Bits(fp);
0741 Read32Bits(fp);
0742 fcount = Read16Bits(fp);
0743 place = place + 30;
0744
0745 if(isempty(movieStatus.videoDescription))
0746 movieStatus.videoDescription = ...
0747 repmat(struct('type',[],'tqual',[],'squal',[],...
0748 'width',[],'height',[]),1,1);
0749 else
0750 movieStatus.videoDescription(end+1)=movieStatus.videoDescription(end);
0751 end
0752 movieStatus.videoDescription(reference_index).type = type;
0753 movieStatus.videoDescription(reference_index).tqual = tqual;
0754 movieStatus.videoDescription(reference_index).squal = squal;
0755 movieStatus.videoDescription(reference_index).width = width;
0756 movieStatus.videoDescription(reference_index).height = height;
0757
0758
0759
0760
0761
0762
0763
0764 end
0765 if size-place < 0
0766 error('got out of sync while reading QT sample description.');
0767 end
0768 fseek(fp, size-place, 'cof');
0769
0770
0771
0772
0773
0774
0775 function i = Read32Bits(fp, count)
0776 if nargin < 2
0777 count = 1;
0778 end
0779 i = fread(fp, count, 'int32');
0780
0781
0782 function i = Read16Bits(fp, count)
0783 if nargin < 2
0784 count = 1;
0785 end
0786 i = fread(fp, count, 'int16');
0787
0788 function i = Read4ByteString(fp)
0789 i = char(fread(fp, 4, 'int8')');