annotate toolboxes/mp3readwrite/mp3read.m @ 0:e9a9cd732c1e tip

first hg version after svn
author wolffd
date Tue, 10 Feb 2015 15:05:51 +0000
parents
children
rev   line source
wolffd@0 1 function [Y,FS,NBITS,OPTS] = mp3read(FILE,N,MONO,DOWNSAMP,DELAY)
wolffd@0 2 % MP3READ Read MP3 audio file via use of external binaries.
wolffd@0 3 % Y = MP3READ(FILE) reads an mp3-encoded audio file into the
wolffd@0 4 % vector Y just like wavread reads a wav-encoded file (one channel
wolffd@0 5 % per column). Extension ".mp3" is added if FILE has none.
wolffd@0 6 % Also accepts other formats of wavread, such as
wolffd@0 7 % Y = MP3READ(FILE,N) to read just the first N sample frames (N
wolffd@0 8 % scalar), or the frames from N(1) to N(2) if N is a two-element vector.
wolffd@0 9 % Y = MP3READ(FILE,FMT) or Y = mp3read(FILE,N,FMT)
wolffd@0 10 % with FMT as 'native' returns int16 samples instead of doubles;
wolffd@0 11 % FMT can be 'double' for default behavior (to exactly mirror the
wolffd@0 12 % syntax of wavread).
wolffd@0 13 %
wolffd@0 14 % [Y,FS,NBITS,OPTS] = MP3READ(FILE...) returns extra information:
wolffd@0 15 % FS is the sampling rate, NBITS is the bit depth (always 16),
wolffd@0 16 % OPTS.fmt is a format info string; OPTS has multiple other
wolffd@0 17 % fields, see WAVREAD.
wolffd@0 18 %
wolffd@0 19 % SIZ = MP3READ(FILE,'size') returns the size of the audio data contained
wolffd@0 20 % in the file in place of the actual audio data, returning the
wolffd@0 21 % 2-element vector SIZ=[samples channels].
wolffd@0 22 %
wolffd@0 23 % [Y...] = MP3READ(FILE,N,MONO,DOWNSAMP,DELAY) extends the
wolffd@0 24 % WAVREAD syntax to allow access to special features of the
wolffd@0 25 % mpg123 engine: MONO = 1 forces output to be mono (by
wolffd@0 26 % averaging stereo channels); DOWNSAMP = 2 or 4 downsamples by
wolffd@0 27 % a factor of 2 or 4 (thus FS returns as 22050 or 11025
wolffd@0 28 % respectively for a 44 kHz mp3 file);
wolffd@0 29 % To accommodate a bug in mpg123-0.59, DELAY controls how many
wolffd@0 30 % "warm up" samples to drop at the start of the file; the
wolffd@0 31 % default value of 2257 makes an mp3write/mp3read loop for a 44
wolffd@0 32 % kHz mp3 file be as close as possible to being temporally
wolffd@0 33 % aligned; specify as 0 to prevent discard of initial samples.
wolffd@0 34 % For later versions of mpg123 (e.g. 1.9.0) this is not needed;
wolffd@0 35 % a flag in mp3read.m makes the default DELAY zero in this case.
wolffd@0 36 %
wolffd@0 37 % [Y...] = MP3READ(URL...) uses the built-in network
wolffd@0 38 % functionality of mpg123 to read an MP3 file across the
wolffd@0 39 % network. URL must be of the form 'http://...' or
wolffd@0 40 % 'ftp://...'. 'size' and OPTS are not available in this mode.
wolffd@0 41 %
wolffd@0 42 % Example:
wolffd@0 43 % To read an mp3 file as doubles at its original width and sampling rate:
wolffd@0 44 % [Y,FS] = mp3read('piano.mp3');
wolffd@0 45 % To read the first 1 second of the same file, downsampled by a
wolffd@0 46 % factor of 4, cast to mono, using the default filename
wolffd@0 47 % extension:
wolffd@0 48 % [Y,FS4] = mp3read('piano', FS/4, 1, 4);
wolffd@0 49 %
wolffd@0 50 % Note: Because the mp3 format encodes samples in blocks of 26 ms (at
wolffd@0 51 % 44 kHz), and because of the "warm up" period of the encoder,
wolffd@0 52 % the file length may not be exactly what you expect, depending
wolffd@0 53 % on your version of mpg123 (recent versions fix warmup).
wolffd@0 54 %
wolffd@0 55 % Note: requires external binaries mpg123 and mp3info; you
wolffd@0 56 % can find binaries for several platforms at:
wolffd@0 57 % http://labrosa.ee.columbia.edu/matlab/mp3read.html
wolffd@0 58 %
wolffd@0 59 % See also mp3write, wavread.
wolffd@0 60
wolffd@0 61 % $Header: /Users/dpwe/matlab/columbiafns/RCS/mp3read.m,v 1.7 2010/04/09 18:13:00 dpwe Exp dpwe $
wolffd@0 62
wolffd@0 63 % 2003-07-20 dpwe@ee.columbia.edu This version calls mpg123.
wolffd@0 64 % 2004-08-31 Fixed to read whole files correctly
wolffd@0 65 % 2004-09-08 Uses mp3info to get info about mp3 files too
wolffd@0 66 % 2004-09-18 Reports all mp3info fields in OPTS.fmt; handles MPG2LSF sizes
wolffd@0 67 % + added MONO, DOWNSAMP flags, changed default behavior.
wolffd@0 68 % 2005-09-28 Fixed bug reading full-rate stereo as 1ch (thx bjoerns@vjk.dk)
wolffd@0 69 % 2006-09-17 Chop off initial 2257 sample delay (for 44.1 kHz mp3)
wolffd@0 70 % so read-write loop doesn't get progressively delayed.
wolffd@0 71 % You can suppress this with a 5th argument of 0.
wolffd@0 72 % 2007-02-04 Added support for FMT argument to match wavread
wolffd@0 73 % Added automatic selection of binary etc. to allow it
wolffd@0 74 % to work cross-platform without editing prior to
wolffd@0 75 % submitting to Matlab File Exchange
wolffd@0 76 % 2007-07-23 Tweaks to 'size' mode so it exactly agrees with read data.
wolffd@0 77 % 2009-03-15 Added fixes so 'http://...' file URLs will work.
wolffd@0 78 % 2009-03-26 Added filename length check to http: test (thx fabricio guzman)
wolffd@0 79
wolffd@0 80 % find our baseline directory
wolffd@0 81 path = fileparts(which('mp3read'));
wolffd@0 82
wolffd@0 83 % %%%%% Directory for temporary file (if needed)
wolffd@0 84 % % Try to read from environment, or use /tmp if it exists, or use CWD
wolffd@0 85 tmpdir = getenv('TMPDIR');
wolffd@0 86 if isempty(tmpdir) || exist(tmpdir,'file')==0
wolffd@0 87 tmpdir = '/tmp';
wolffd@0 88 end
wolffd@0 89 if exist(tmpdir,'file')==0
wolffd@0 90 tmpdir = '';
wolffd@0 91 end
wolffd@0 92 % ensure it exists
wolffd@0 93 %if length(tmpdir) > 0 && exist(tmpdir,'file')==0
wolffd@0 94 % mkdir(tmpdir);
wolffd@0 95 %end
wolffd@0 96
wolffd@0 97 %%%%%% Command to delete temporary file (if needed)
wolffd@0 98 rmcmd = 'rm';
wolffd@0 99
wolffd@0 100 %%%%%% Location of the binaries - attempt to choose automatically
wolffd@0 101 %%%%%% (or edit to be hard-coded for your installation)
wolffd@0 102 ext = lower(computer);
wolffd@0 103 if ispc
wolffd@0 104 ext = 'exe';
wolffd@0 105 rmcmd = 'del';
wolffd@0 106 end
wolffd@0 107 % mpg123-0.59 inserts silence at the start of decoded files, which
wolffd@0 108 % we compensate. However, this is fixed in mpg123-1.9.0, so
wolffd@0 109 % make this flag 1 only if you have mpg123-0.5.9
wolffd@0 110 MPG123059 = 0;
wolffd@0 111 mpg123 = fullfile(path,['mpg123.',ext]);
wolffd@0 112 mp3info = fullfile(path,['mp3info.',ext]);
wolffd@0 113
wolffd@0 114 %%%%% Check for network mode
wolffd@0 115 if length(FILE) > 6 && (strcmp(lower(FILE(1:7)),'http://') == 1 ...
wolffd@0 116 || strcmp(lower(FILE(1:6)),'ftp://'))
wolffd@0 117 % mp3info not available over network
wolffd@0 118 OVERNET = 1;
wolffd@0 119 else
wolffd@0 120 OVERNET = 0;
wolffd@0 121 end
wolffd@0 122
wolffd@0 123
wolffd@0 124 %%%%% Process input arguments
wolffd@0 125 if nargin < 2
wolffd@0 126 N = 0;
wolffd@0 127 end
wolffd@0 128
wolffd@0 129 % Check for FMT spec (per wavread)
wolffd@0 130 FMT = 'double';
wolffd@0 131 if ischar(N)
wolffd@0 132 FMT = lower(N);
wolffd@0 133 N = 0;
wolffd@0 134 end
wolffd@0 135
wolffd@0 136 if length(N) == 1
wolffd@0 137 % Specified N was upper limit
wolffd@0 138 N = [1 N];
wolffd@0 139 end
wolffd@0 140 if nargin < 3
wolffd@0 141 forcemono = 0;
wolffd@0 142 else
wolffd@0 143 % Check for 3rd arg as FMT
wolffd@0 144 if ischar(MONO)
wolffd@0 145 FMT = lower(MONO);
wolffd@0 146 MONO = 0;
wolffd@0 147 end
wolffd@0 148 forcemono = (MONO ~= 0);
wolffd@0 149 end
wolffd@0 150 if nargin < 4
wolffd@0 151 downsamp = 1;
wolffd@0 152 else
wolffd@0 153 downsamp = DOWNSAMP;
wolffd@0 154 end
wolffd@0 155 if downsamp ~= 1 && downsamp ~= 2 && downsamp ~= 4
wolffd@0 156 error('DOWNSAMP can only be 1, 2, or 4');
wolffd@0 157 end
wolffd@0 158
wolffd@0 159 % process DELAY option (nargin 5) after we've read the SR
wolffd@0 160
wolffd@0 161 if strcmp(FMT,'native') == 0 && strcmp(FMT,'double') == 0 && ...
wolffd@0 162 strcmp(FMT,'size') == 0
wolffd@0 163 error(['FMT must be ''native'' or ''double'' (or ''size''), not ''',FMT,'''']);
wolffd@0 164 end
wolffd@0 165
wolffd@0 166
wolffd@0 167 %%%%%% Constants
wolffd@0 168 NBITS=16;
wolffd@0 169
wolffd@0 170 %%%%% add extension if none (like wavread)
wolffd@0 171 [path,file,ext] = fileparts(FILE);
wolffd@0 172 if isempty(ext)
wolffd@0 173 FILE = [FILE, '.mp3'];
wolffd@0 174 end
wolffd@0 175
wolffd@0 176 %%%%% maybe expand ~ %%%%%%
wolffd@0 177 if FILE(1) == '~'
wolffd@0 178 FILE = [getenv('HOME'),FILE(2:end)];
wolffd@0 179 end
wolffd@0 180
wolffd@0 181
wolffd@0 182 if ~OVERNET
wolffd@0 183 %%%%%% Probe file to find format, size, etc. using "mp3info" utility
wolffd@0 184 cmd = ['"',mp3info, '" -r m -p "%Q %u %b %r %v * %C %e %E %L %O %o %p" "', FILE,'"'];
wolffd@0 185 % Q = samprate, u = #frames, b = #badframes (needed to get right answer from %u)
wolffd@0 186 % r = bitrate, v = mpeg version (1/2/2.5)
wolffd@0 187 % C = Copyright, e = emph, E = CRC, L = layer, O = orig, o = mono, p = pad
wolffd@0 188 w = mysystem(cmd);
wolffd@0 189 % Break into numerical and ascii parts by finding the delimiter we put in
wolffd@0 190 starpos = findstr(w,'*');
wolffd@0 191 nums = str2num(w(1:(starpos - 2)));
wolffd@0 192 strs = tokenize(w((starpos+2):end));
wolffd@0 193
wolffd@0 194 SR = nums(1);
wolffd@0 195 nframes = nums(2);
wolffd@0 196 nchans = 2 - strcmp(strs{6}, 'mono');
wolffd@0 197 layer = length(strs{4});
wolffd@0 198 bitrate = nums(4)*1000;
wolffd@0 199 mpgv = nums(5);
wolffd@0 200 % Figure samples per frame, after
wolffd@0 201 % http://board.mp3-tech.org/view.php3?bn=agora_mp3techorg&key=1019510889
wolffd@0 202 if layer == 1
wolffd@0 203 smpspfrm = 384;
wolffd@0 204 elseif SR < 32000 && layer ==3
wolffd@0 205 smpspfrm = 576;
wolffd@0 206 if mpgv == 1
wolffd@0 207 error('SR < 32000 but mpeg version = 1');
wolffd@0 208 end
wolffd@0 209 else
wolffd@0 210 smpspfrm = 1152;
wolffd@0 211 end
wolffd@0 212
wolffd@0 213 OPTS.fmt.mpgBitrate = bitrate;
wolffd@0 214 OPTS.fmt.mpgVersion = mpgv;
wolffd@0 215 % fields from wavread's OPTS
wolffd@0 216 OPTS.fmt.nAvgBytesPerSec = bitrate/8;
wolffd@0 217 OPTS.fmt.nSamplesPerSec = SR;
wolffd@0 218 OPTS.fmt.nChannels = nchans;
wolffd@0 219 OPTS.fmt.nBlockAlign = smpspfrm/SR*bitrate/8;
wolffd@0 220 OPTS.fmt.nBitsPerSample = NBITS;
wolffd@0 221 OPTS.fmt.mpgNFrames = nframes;
wolffd@0 222 OPTS.fmt.mpgCopyright = strs{1};
wolffd@0 223 OPTS.fmt.mpgEmphasis = strs{2};
wolffd@0 224 OPTS.fmt.mpgCRC = strs{3};
wolffd@0 225 OPTS.fmt.mpgLayer = strs{4};
wolffd@0 226 OPTS.fmt.mpgOriginal = strs{5};
wolffd@0 227 OPTS.fmt.mpgChanmode = strs{6};
wolffd@0 228 OPTS.fmt.mpgPad = strs{7};
wolffd@0 229 OPTS.fmt.mpgSampsPerFrame = smpspfrm;
wolffd@0 230 else
wolffd@0 231 % OVERNET mode
wolffd@0 232 OPTS = [];
wolffd@0 233 % guesses
wolffd@0 234 smpspfrm = 1152;
wolffd@0 235 SR = 44100;
wolffd@0 236 nframes = 0;
wolffd@0 237 end
wolffd@0 238
wolffd@0 239 if SR == 16000 && downsamp == 4
wolffd@0 240 error('mpg123 will not downsample 16 kHz files by 4 (only 2)');
wolffd@0 241 end
wolffd@0 242
wolffd@0 243 % from libmpg123/frame.h
wolffd@0 244 GAPLESS_DELAY = 529;
wolffd@0 245
wolffd@0 246 % process or set delay
wolffd@0 247 if nargin < 5
wolffd@0 248
wolffd@0 249 if MPG123059
wolffd@0 250 mpg123delay44kHz = 2257; % empirical delay of lame/mpg123 loop
wolffd@0 251 mpg123delay16kHz = 1105; % empirical delay of lame/mpg123 loop
wolffd@0 252 % for 16 kHz sampling - one 1152
wolffd@0 253 % sample frame less??
wolffd@0 254 if SR == 16000
wolffd@0 255 rawdelay = mpg123delay16kHz;
wolffd@0 256 else
wolffd@0 257 rawdelay = mpg123delay44kHz; % until we know better
wolffd@0 258 end
wolffd@0 259 delay = round(rawdelay/downsamp);
wolffd@0 260 else
wolffd@0 261 % seems like predelay is fixed in mpg123-1.9.0
wolffd@0 262 delay = 0;
wolffd@0 263 end
wolffd@0 264 else
wolffd@0 265 delay = DELAY;
wolffd@0 266 end
wolffd@0 267
wolffd@0 268 if downsamp == 1
wolffd@0 269 downsampstr = '';
wolffd@0 270 else
wolffd@0 271 downsampstr = [' -',num2str(downsamp)];
wolffd@0 272 end
wolffd@0 273 FS = SR/downsamp;
wolffd@0 274
wolffd@0 275 if forcemono == 1
wolffd@0 276 nchans = 1;
wolffd@0 277 chansstr = ' -m';
wolffd@0 278 else
wolffd@0 279 chansstr = '';
wolffd@0 280 end
wolffd@0 281
wolffd@0 282 % Size-reading version
wolffd@0 283 if strcmp(FMT,'size') == 1
wolffd@0 284 if MPG123059
wolffd@0 285 Y = [floor(smpspfrm*nframes/downsamp)-delay, nchans];
wolffd@0 286 else
wolffd@0 287 Y = [floor(smpspfrm*nframes/downsamp)-GAPLESS_DELAY, nchans];
wolffd@0 288 end
wolffd@0 289 else
wolffd@0 290
wolffd@0 291 % Temporary file to use
wolffd@0 292 tmpfile = fullfile(tmpdir, ['tmp',num2str(round(1000*rand(1))),'.wav']);
wolffd@0 293
wolffd@0 294 skipx = 0;
wolffd@0 295 skipblks = 0;
wolffd@0 296 skipstr = '';
wolffd@0 297 sttfrm = N(1)-1;
wolffd@0 298
wolffd@0 299 % chop off transcoding delay?
wolffd@0 300 %sttfrm = sttfrm + delay; % empirically measured
wolffd@0 301 % no, we want to *decode* those samples, then drop them
wolffd@0 302 % so delay gets added to skipx instead
wolffd@0 303
wolffd@0 304 if sttfrm > 0
wolffd@0 305 skipblks = floor(sttfrm*downsamp/smpspfrm);
wolffd@0 306 skipx = sttfrm - (skipblks*smpspfrm/downsamp);
wolffd@0 307 skipstr = [' -k ', num2str(skipblks)];
wolffd@0 308 end
wolffd@0 309 skipx = skipx + delay;
wolffd@0 310
wolffd@0 311 lenstr = '';
wolffd@0 312 endfrm = -1;
wolffd@0 313 decblk = 0;
wolffd@0 314 if length(N) > 1
wolffd@0 315 endfrm = N(2);
wolffd@0 316 if endfrm > sttfrm
wolffd@0 317 decblk = ceil((endfrm+delay)*downsamp/smpspfrm) - skipblks + 10;
wolffd@0 318 % we read 10 extra blks (+10) to cover the case where up to 10 bad
wolffd@0 319 % blocks are included in the part we are trying to read (it happened)
wolffd@0 320 lenstr = [' -n ', num2str(decblk)];
wolffd@0 321 % This generates a spurious "Warn: requested..." if reading right
wolffd@0 322 % to the last sample by index (or bad blks), but no matter.
wolffd@0 323 end
wolffd@0 324 end
wolffd@0 325
wolffd@0 326 % Run the decode
wolffd@0 327 cmd=['"',mpg123,'"', downsampstr, chansstr, skipstr, lenstr, ...
wolffd@0 328 ' -q -w "', tmpfile,'" "',FILE,'"'];
wolffd@0 329 %w =
wolffd@0 330 mysystem(cmd);
wolffd@0 331
wolffd@0 332 % Load the data (may update FS if it was based on a guess previously)
wolffd@0 333 [Y,FS] = wavread(tmpfile);
wolffd@0 334
wolffd@0 335 % % pad delay on to end, just in case
wolffd@0 336 % Y = [Y; zeros(delay,size(Y,2))];
wolffd@0 337 % % no, the saved file is just longer
wolffd@0 338
wolffd@0 339 if decblk > 0 && length(Y) < decblk*smpspfrm/downsamp
wolffd@0 340 % This will happen if the selected block range includes >1 bad block
wolffd@0 341 disp(['Warn: requested ', num2str(decblk*smpspfrm/downsamp),' frames, returned ',num2str(length(Y))]);
wolffd@0 342 end
wolffd@0 343
wolffd@0 344 % Delete tmp file
wolffd@0 345 mysystem([rmcmd,' "', tmpfile,'"']);
wolffd@0 346
wolffd@0 347 % debug
wolffd@0 348 % disp(['sttfrm=',num2str(sttfrm),' endfrm=',num2str(endfrm),' skipx=',num2str(skipx),' delay=',num2str(delay),' len=',num2str(length(Y))]);
wolffd@0 349
wolffd@0 350 % Select the desired part
wolffd@0 351 if skipx+endfrm-sttfrm > length(Y)
wolffd@0 352 endfrm = length(Y)+sttfrm-skipx;
wolffd@0 353 end
wolffd@0 354
wolffd@0 355 if endfrm > sttfrm
wolffd@0 356 Y = Y(skipx+(1:(endfrm-sttfrm)),:);
wolffd@0 357 elseif skipx > 0
wolffd@0 358 Y = Y((skipx+1):end,:);
wolffd@0 359 end
wolffd@0 360
wolffd@0 361 % Convert to int if format = 'native'
wolffd@0 362 if strcmp(FMT,'native')
wolffd@0 363 Y = int16((2^15)*Y);
wolffd@0 364 end
wolffd@0 365
wolffd@0 366 end
wolffd@0 367
wolffd@0 368 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
wolffd@0 369 function w = mysystem(cmd)
wolffd@0 370 % Run system command; report error; strip all but last line
wolffd@0 371 [s,w] = system(cmd);
wolffd@0 372 if s ~= 0
wolffd@0 373 error(['unable to execute ',cmd,' (',w,')']);
wolffd@0 374 end
wolffd@0 375 % Keep just final line
wolffd@0 376 w = w((1+max([0,findstr(w,10)])):end);
wolffd@0 377 % Debug
wolffd@0 378 %disp([cmd,' -> ','*',w,'*']);
wolffd@0 379
wolffd@0 380 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
wolffd@0 381 function a = tokenize(s,t)
wolffd@0 382 % Break space-separated string into cell array of strings.
wolffd@0 383 % Optional second arg gives alternate separator (default ' ')
wolffd@0 384 % 2004-09-18 dpwe@ee.columbia.edu
wolffd@0 385 if nargin < 2; t = ' '; end
wolffd@0 386 a = [];
wolffd@0 387 p = 1;
wolffd@0 388 n = 1;
wolffd@0 389 l = length(s);
wolffd@0 390 nss = findstr([s(p:end),t],t);
wolffd@0 391 for ns = nss
wolffd@0 392 % Skip initial spaces (separators)
wolffd@0 393 if ns == p
wolffd@0 394 p = p+1;
wolffd@0 395 else
wolffd@0 396 if p <= l
wolffd@0 397 a{n} = s(p:(ns-1));
wolffd@0 398 n = n+1;
wolffd@0 399 p = ns+1;
wolffd@0 400 end
wolffd@0 401 end
wolffd@0 402 end
wolffd@0 403