videoframets.m 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. function ts=videoframets(varargin)
  2. %VIDEOFRAMETS Find timestamps associated with frames in an mpeg file
  3. %[v0.1, by David Bulkin, 1/22/2017]
  4. %
  5. % ts = VIDEOFRAMETS(ffmpeg_path,videofile) returns the timestamps (ts) in seconds of
  6. % each video frame in a video file (videofile) using ffmpeg.exe. The location of
  7. % ffmpeg.exe must be specified (ffmpeg_path; i.e. it can not just reside on the
  8. % matlab path).
  9. %
  10. % VIDEOFRAMETS(ffmpeg_path,videofile,ffmpeg_textfile) will store the raw output
  11. % produced by ffmpeg in the text file 'ffmpeg_textfile'
  12. %
  13. % FFMPEG is freely available can be downloaded from:
  14. % <a href="matlab:web('http://ffmpeg.org/')">http://ffmpeg.org/</a>
  15. %
  16. % Tested on Windows, with ffmpeg 2.5.2 and 3.2.2
  17. % I think this should work on OSX/UNIX, because the pipe to file commands are
  18. % the same, but I haven't tested it.
  19. %
  20. % EXAMPLE:
  21. % ffpath='C:\ffmpeg\bin'
  22. % vidfile='D:\videos\somevideo.mpeg'
  23. % ts=videoframets(ffpath,somevideo);
  24. % hist(1./diff(ts),1000); %this shows a distribution of the actual frame rates
  25. %
  26. %
  27. % Notes on why this is useful:
  28. % VIDEOFRAMETS attempts to solve an issue with quantitative video analysis. While
  29. % mpeg video is recorded with a fixed framerate, in reality, video frames often
  30. % deviate from this value. [I think this is due to delays in the encoder, for
  31. % instance if the computer recording video is under excessive load, frames will come
  32. % in slower]. If frames often come in a little bit slower than expected (empirical
  33. % framerate < nominal framerate), the problem compounds over the course of a video:
  34. % if a user assumes a fixed framerate the data at the end of the video will be
  35. % greatly mismatched from reality.
  36. % This presents a tremendous problem to time locked video analysis. Matlab's
  37. % VideoReader offers a practical solution: VideoReader objects contain a property
  38. % that identifies/sets the current time in the video. This is generally a practical
  39. % solution to the problem (note: when used with the newer .readFrame method rather
  40. % than the older .read method). However, this has some limitations:
  41. % 1: It is often useful to know the number of frames and the corresponding
  42. % timestamps at the onset of video analysis (although a rough estimate is
  43. % obtainable using the .FrameRate and .Duration properties of VideoReader,
  44. % typically adequate for initializing variables).
  45. % 2: The performance of vision.VideoFileReader is often orders of magnitude
  46. % faster than VideoReader (from personal measurements, speed improvements
  47. % depend on codec), and seems to work more reliably with a wider set of video
  48. % files. However, vision.VideoFileReader offers no strategy for assessing
  49. % frame times. Using VideoReader for timing and vision.VideoFileReader for frame
  50. % data is impractical because it sacrifices the advantages of using the
  51. % vision.VideoFileReader function
  52. % 3: It may be useful to some users to get timing information only, for instance
  53. % if performing video analysis elsewhere.
  54. %
  55. % The times of frames are stored in the MPEG file using presentation timestamps
  56. % (PTS; frequently used for alignment of video and auditory streams). While these
  57. % are inaccessible to the high level Matlab coder, the tool ffmpeg readily provides
  58. % their values, and runs pretty quickly (in testing, 50 seconds for a 20 minute
  59. % MPEG file, while VideoReader gets the same information in about 6 minutes).
  60. %
  61. % See also: VideoReader vision.VideoFileReader
  62. %% Check inputs:
  63. p=inputParser;
  64. addRequired(p,'ffm_fp',@(x) validateattributes(x,{'char'},{'nonempty'},'VIDEOFRAMETS','ffmpeg_path',1))
  65. addRequired(p,'vid_fn',@(x) validateattributes(x,{'char'},{'nonempty'},'VIDEOFRAMETS','videofile',2))
  66. addOptional(p,'txt_fn',tempname,@(x) validateattributes(x,{'char'},{'nonempty'},'VIDEOFRAMETS','ffmpeg_textfile',23))
  67. parse(p,varargin{:})
  68. %Further checks (with useful error messages)
  69. ffm=fullfile(p.Results.ffm_fp,'ffmpeg.exe');
  70. if exist(ffm,'file')~=2
  71. error('Could not find ffmpeg.exe in %s',p.Results.ffm_fp);
  72. end
  73. if exist(p.Results.vid_fn,'file')~=2
  74. error('Could not find the video file %s',p.Results.vid_fn);
  75. end
  76. %% Call FFMPEG with appropriate flags:
  77. %a few notes on the ffmpeg command:
  78. % 1: It's apparently quicker to have ffmpeg pipe the output to a text file and
  79. % have matlab read the text file, rather than to just have matlab read the output
  80. % from system (that's really surprising to me!). It's actually orders of
  81. % magnitude faster (?)
  82. %
  83. % 2: in my tests with ffmpeg, I found that ffmpeg's showinfo surprisingly seems to
  84. % put its output into the stderr output (i.e. use 2> rather than >)...this took me
  85. % FOREVER to figure out!
  86. ffm_line=sprintf('"%s" -i "%s" -f null -vf showinfo - 2>%s',ffm,p.Results.vid_fn,p.Results.txt_fn);
  87. fprintf('Processing %s\n',p.Results.vid_fn);
  88. system(ffm_line);
  89. %% Because output was sent to a file, now it must be read back in...
  90. %There's a challenge with reading the file output: textscan() won't work well with the
  91. %file format, nor will any of the other high level readers. fgetl() is probably the best
  92. %bet, but it's challenging to initialize ts, as it's unclear how many lines the file will
  93. %contain (and non-header lines are allowed to not include timestamp info). My best
  94. %solution is to run through once to get the number of lines (that contain frame times) in
  95. %the file, and then run through again to get the actual times:
  96. fid=fopen(p.Results.txt_fn);
  97. %count the number of lines containing frame times (i.e. number of frames)
  98. i=0;
  99. tline=1;
  100. while tline~=-1
  101. tline=fgetl(fid);
  102. startloc=strfind(tline,'pts_time:')+numel('pts_time:');
  103. if ~isempty(startloc)
  104. i=i+1;
  105. end
  106. end
  107. ts=nan(i,1);
  108. frewind(fid)
  109. tline=0;i=1;
  110. while tline~=-1
  111. tline=fgetl(fid);
  112. startloc=strfind(tline,'pts_time:')+numel('pts_time:');
  113. if ~isempty(startloc)
  114. endloc=strfind(tline,' pos:');
  115. ts(i)=str2double(tline(startloc:endloc));
  116. i=i+1;
  117. end
  118. end
  119. fclose(fid);
  120. %% Finally, just delete any temporary files
  121. if ~isempty(p.UsingDefaults)
  122. delete(p.Results.txt_fn)
  123. end