Daniel@0: function [Y,FS,NBITS,OPTS] = mp3read(FILE,N,MONO,DOWNSAMP,DELAY) Daniel@0: % MP3READ Read MP3 audio file via use of external binaries. Daniel@0: % Y = MP3READ(FILE) reads an mp3-encoded audio file into the Daniel@0: % vector Y just like wavread reads a wav-encoded file (one channel Daniel@0: % per column). Extension ".mp3" is added if FILE has none. Daniel@0: % Also accepts other formats of wavread, such as Daniel@0: % Y = MP3READ(FILE,N) to read just the first N sample frames (N Daniel@0: % scalar), or the frames from N(1) to N(2) if N is a two-element vector. Daniel@0: % Y = MP3READ(FILE,FMT) or Y = mp3read(FILE,N,FMT) Daniel@0: % with FMT as 'native' returns int16 samples instead of doubles; Daniel@0: % FMT can be 'double' for default behavior (to exactly mirror the Daniel@0: % syntax of wavread). Daniel@0: % Daniel@0: % [Y,FS,NBITS,OPTS] = MP3READ(FILE...) returns extra information: Daniel@0: % FS is the sampling rate, NBITS is the bit depth (always 16), Daniel@0: % OPTS.fmt is a format info string; OPTS has multiple other Daniel@0: % fields, see WAVREAD. Daniel@0: % Daniel@0: % SIZ = MP3READ(FILE,'size') returns the size of the audio data contained Daniel@0: % in the file in place of the actual audio data, returning the Daniel@0: % 2-element vector SIZ=[samples channels]. Daniel@0: % Daniel@0: % [Y...] = MP3READ(FILE,N,MONO,DOWNSAMP,DELAY) extends the Daniel@0: % WAVREAD syntax to allow access to special features of the Daniel@0: % mpg123 engine: MONO = 1 forces output to be mono (by Daniel@0: % averaging stereo channels); DOWNSAMP = 2 or 4 downsamples by Daniel@0: % a factor of 2 or 4 (thus FS returns as 22050 or 11025 Daniel@0: % respectively for a 44 kHz mp3 file); DELAY controls how many Daniel@0: % "warm up" samples to drop at the start of the file; the Daniel@0: % default value of 2257 makes an mp3write/mp3read loop for a 44 Daniel@0: % kHz mp3 file be as close as possible to being temporally Daniel@0: % aligned; specify as 0 to prevent discard of initial samples. Daniel@0: % Daniel@0: % Example: Daniel@0: % To read an mp3 file as doubles at its original width and sampling rate: Daniel@0: % [Y,FS] = mp3read('piano.mp3'); Daniel@0: % To read the first 1 second of the same file, downsampled by a Daniel@0: % factor of 4, cast to mono, using the default filename Daniel@0: % extension: Daniel@0: % [Y,FS4] = mp3read('piano', FS/4, 1, 4); Daniel@0: % Daniel@0: % Note: Because the mp3 format encodes samples in blocks of 26 ms (at Daniel@0: % 44 kHz), and because of the "warm up" period of the encoder, Daniel@0: % the file length may not be exactly what you expect. Daniel@0: % Daniel@0: % Note: requires external binaries mpg123 and mp3info; you Daniel@0: % can find binaries for several platforms at: Daniel@0: % http://labrosa.ee.columbia.edu/matlab/mp3read.html Daniel@0: % Daniel@0: % See also mp3write, wavread. Daniel@0: Daniel@0: % 2003-07-20 dpwe@ee.columbia.edu This version calls mpg123. Daniel@0: % 2004-08-31 Fixed to read whole files correctly Daniel@0: % 2004-09-08 Uses mp3info to get info about mp3 files too Daniel@0: % 2004-09-18 Reports all mp3info fields in OPTS.fmt; handles MPG2LSF sizes Daniel@0: % + added MONO, DOWNSAMP flags, changed default behavior. Daniel@0: % 2005-09-28 Fixed bug reading full-rate stereo as 1ch (thx bjoerns@vjk.dk) Daniel@0: % 2006-09-17 Chop off initial 2257 sample delay (for 44.1 kHz mp3) Daniel@0: % so read-write loop doesn't get progressively delayed. Daniel@0: % You can suppress this with a 5th argument of 0. Daniel@0: % 2007-02-04 Added support for FMT argument to match wavread Daniel@0: % Added automatic selection of binary etc. to allow it Daniel@0: % to work cross-platform without editing prior to Daniel@0: % submitting to Matlab File Exchange Daniel@0: % 2007-07-23 Tweaks to 'size' mode so it exactly agrees with read data. Daniel@0: Daniel@0: % find our baseline directory Daniel@0: path = fileparts(which('mp3read')); Daniel@0: Daniel@0: % %%%%% Directory for temporary file (if needed) Daniel@0: % % Try to read from environment, or use /tmp if it exists, or use CWD Daniel@0: tmpdir = getenv('TMPDIR'); Daniel@0: if isempty(tmpdir) || exist(tmpdir,'file')==0 Daniel@0: tmpdir = '/tmp'; Daniel@0: end Daniel@0: if exist(tmpdir,'file')==0 Daniel@0: tmpdir = ''; Daniel@0: end Daniel@0: % ensure it exists Daniel@0: %if length(tmpdir) > 0 && exist(tmpdir,'file')==0 Daniel@0: % mkdir(tmpdir); Daniel@0: %end Daniel@0: Daniel@0: %%%%%% Command to delete temporary file (if needed) Daniel@0: rmcmd = 'rm'; Daniel@0: Daniel@0: %%%%%% Location of the binaries - attempt to choose automatically Daniel@0: %%%%%% (or edit to be hard-coded for your installation) Daniel@0: ext = lower(computer); Daniel@0: if ispc Daniel@0: ext = 'exe'; Daniel@0: rmcmd = 'del'; Daniel@0: end Daniel@0: mpg123 = fullfile(path,['mpg123.',ext]); Daniel@0: mp3info = fullfile(path,['mp3info.',ext]); Daniel@0: Daniel@0: %%%%% Process input arguments Daniel@0: if nargin < 2 Daniel@0: N = 0; Daniel@0: end Daniel@0: Daniel@0: % Check for FMT spec (per wavread) Daniel@0: FMT = 'double'; Daniel@0: if ischar(N) Daniel@0: FMT = lower(N); Daniel@0: N = 0; Daniel@0: end Daniel@0: Daniel@0: if length(N) == 1 Daniel@0: % Specified N was upper limit Daniel@0: N = [1 N]; Daniel@0: end Daniel@0: if nargin < 3 Daniel@0: forcemono = 0; Daniel@0: else Daniel@0: % Check for 3rd arg as FMT Daniel@0: if ischar(MONO) Daniel@0: FMT = lower(MONO); Daniel@0: MONO = 0; Daniel@0: end Daniel@0: forcemono = (MONO ~= 0); Daniel@0: end Daniel@0: if nargin < 4 Daniel@0: downsamp = 1; Daniel@0: else Daniel@0: downsamp = DOWNSAMP; Daniel@0: end Daniel@0: if downsamp ~= 1 && downsamp ~= 2 && downsamp ~= 4 Daniel@0: error('DOWNSAMP can only be 1, 2, or 4'); Daniel@0: end Daniel@0: if nargin < 5 Daniel@0: mpg123delay44kHz = 2257; % empirical delay of lame/mpg123 loop Daniel@0: delay = round(mpg123delay44kHz/downsamp); Daniel@0: else Daniel@0: delay = DELAY; Daniel@0: end Daniel@0: Daniel@0: if strcmp(FMT,'native') == 0 && strcmp(FMT,'double') == 0 && ... Daniel@0: strcmp(FMT,'size') == 0 Daniel@0: error(['FMT must be ''native'' or ''double'' (or ''size''), not ''',FMT,'''']); Daniel@0: end Daniel@0: Daniel@0: Daniel@0: %%%%%% Constants Daniel@0: NBITS=16; Daniel@0: Daniel@0: %%%%% add extension if none (like wavread) Daniel@0: [path,file,ext] = fileparts(FILE); Daniel@0: if isempty(ext) Daniel@0: FILE = [FILE, '.mp3']; Daniel@0: end Daniel@0: Daniel@0: %%%%%% Probe file to find format, size, etc. using "mp3info" utility Daniel@0: cmd = ['"',mp3info, '" -r m -p "%Q %u %b %r %v * %C %e %E %L %O %o %p" "', FILE,'"']; Daniel@0: % Q = samprate, u = #frames, b = #badframes (needed to get right answer from %u) Daniel@0: % r = bitrate, v = mpeg version (1/2/2.5) Daniel@0: % C = Copyright, e = emph, E = CRC, L = layer, O = orig, o = mono, p = pad Daniel@0: w = mysystem(cmd); Daniel@0: % Break into numerical and ascii parts by finding the delimiter we put in Daniel@0: starpos = findstr(w,'*'); Daniel@0: nums = str2num(w(1:(starpos - 2))); Daniel@0: strs = tokenize(w((starpos+2):end)); Daniel@0: Daniel@0: SR = nums(1); Daniel@0: nframes = nums(2); Daniel@0: nchans = 2 - strcmp(strs{6}, 'mono'); Daniel@0: layer = length(strs{4}); Daniel@0: bitrate = nums(4)*1000; Daniel@0: mpgv = nums(5); Daniel@0: % Figure samples per frame, after Daniel@0: % http://board.mp3-tech.org/view.php3?bn=agora_mp3techorg&key=1019510889 Daniel@0: if layer == 1 Daniel@0: smpspfrm = 384; Daniel@0: elseif SR < 32000 && layer ==3 Daniel@0: smpspfrm = 576; Daniel@0: if mpgv == 1 Daniel@0: error('SR < 32000 but mpeg version = 1'); Daniel@0: end Daniel@0: else Daniel@0: smpspfrm = 1152; Daniel@0: end Daniel@0: Daniel@0: OPTS.fmt.mpgBitrate = bitrate; Daniel@0: OPTS.fmt.mpgVersion = mpgv; Daniel@0: % fields from wavread's OPTS Daniel@0: OPTS.fmt.nAvgBytesPerSec = bitrate/8; Daniel@0: OPTS.fmt.nSamplesPerSec = SR; Daniel@0: OPTS.fmt.nChannels = nchans; Daniel@0: OPTS.fmt.nBlockAlign = smpspfrm/SR*bitrate/8; Daniel@0: OPTS.fmt.nBitsPerSample = NBITS; Daniel@0: OPTS.fmt.mpgNFrames = nframes; Daniel@0: OPTS.fmt.mpgCopyright = strs{1}; Daniel@0: OPTS.fmt.mpgEmphasis = strs{2}; Daniel@0: OPTS.fmt.mpgCRC = strs{3}; Daniel@0: OPTS.fmt.mpgLayer = strs{4}; Daniel@0: OPTS.fmt.mpgOriginal = strs{5}; Daniel@0: OPTS.fmt.mpgChanmode = strs{6}; Daniel@0: OPTS.fmt.mpgPad = strs{7}; Daniel@0: OPTS.fmt.mpgSampsPerFrame = smpspfrm; Daniel@0: Daniel@0: if SR == 16000 && downsamp == 4 Daniel@0: error('mpg123 will not downsample 16 kHz files by 4 (only 2)'); Daniel@0: end Daniel@0: Daniel@0: if downsamp == 1 Daniel@0: downsampstr = ''; Daniel@0: else Daniel@0: downsampstr = [' -',num2str(downsamp)]; Daniel@0: end Daniel@0: FS = SR/downsamp; Daniel@0: Daniel@0: if forcemono == 1 Daniel@0: nchans = 1; Daniel@0: chansstr = ' -m'; Daniel@0: else Daniel@0: chansstr = ''; Daniel@0: end Daniel@0: Daniel@0: % Size-reading version Daniel@0: if strcmp(FMT,'size') == 1 Daniel@0: Y = [floor(smpspfrm*nframes/downsamp)-delay, nchans]; Daniel@0: else Daniel@0: Daniel@0: % Temporary file to use Daniel@0: tmpfile = fullfile(tmpdir, ['tmp',num2str(round(1000*rand(1))),'.wav']); Daniel@0: Daniel@0: skipx = 0; Daniel@0: skipblks = 0; Daniel@0: skipstr = ''; Daniel@0: sttfrm = N(1)-1; Daniel@0: Daniel@0: % chop off transcoding delay? Daniel@0: %sttfrm = sttfrm + delay; % empirically measured Daniel@0: % no, we want to *decode* those samples, then drop them Daniel@0: % so delay gets added to skipx instead Daniel@0: Daniel@0: if sttfrm > 0 Daniel@0: skipblks = floor(sttfrm*downsamp/smpspfrm); Daniel@0: skipx = sttfrm - (skipblks*smpspfrm/downsamp); Daniel@0: skipstr = [' -k ', num2str(skipblks)]; Daniel@0: end Daniel@0: skipx = skipx + delay; Daniel@0: Daniel@0: lenstr = ''; Daniel@0: endfrm = -1; Daniel@0: decblk = 0; Daniel@0: if length(N) > 1 Daniel@0: endfrm = N(2); Daniel@0: if endfrm > sttfrm Daniel@0: decblk = ceil((endfrm+delay)*downsamp/smpspfrm) - skipblks + 10; Daniel@0: % we read 10 extra blks (+10) to cover the case where up to 10 bad Daniel@0: % blocks are included in the part we are trying to read (it happened) Daniel@0: lenstr = [' -n ', num2str(decblk)]; Daniel@0: % This generates a spurious "Warn: requested..." if reading right Daniel@0: % to the last sample by index (or bad blks), but no matter. Daniel@0: end Daniel@0: end Daniel@0: Daniel@0: % Run the decode Daniel@0: cmd=['"',mpg123,'"', downsampstr, chansstr, skipstr, lenstr, ... Daniel@0: ' -q -w "', tmpfile,'" "',FILE,'"']; Daniel@0: %w = Daniel@0: mysystem(cmd); Daniel@0: Daniel@0: % Load the data Daniel@0: Y = wavread(tmpfile); Daniel@0: Daniel@0: % % pad delay on to end, just in case Daniel@0: % Y = [Y; zeros(delay,size(Y,2))]; Daniel@0: % % no, the saved file is just longer Daniel@0: Daniel@0: if decblk > 0 && length(Y) < decblk*smpspfrm/downsamp Daniel@0: % This will happen if the selected block range includes >1 bad block Daniel@0: disp(['Warn: requested ', num2str(decblk*smpspfrm/downsamp),' frames, returned ',num2str(length(Y))]); Daniel@0: end Daniel@0: Daniel@0: % Delete tmp file Daniel@0: mysystem([rmcmd,' "', tmpfile,'"']); Daniel@0: Daniel@0: % debug Daniel@0: % disp(['sttfrm=',num2str(sttfrm),' endfrm=',num2str(endfrm),' skipx=',num2str(skipx),' delay=',num2str(delay),' len=',num2str(length(Y))]); Daniel@0: Daniel@0: % Select the desired part Daniel@0: if skipx+endfrm-sttfrm > length(Y) Daniel@0: endfrm = length(Y)+sttfrm-skipx; Daniel@0: end Daniel@0: Daniel@0: if endfrm > sttfrm Daniel@0: Y = Y(skipx+(1:(endfrm-sttfrm)),:); Daniel@0: elseif skipx > 0 Daniel@0: Y = Y((skipx+1):end,:); Daniel@0: end Daniel@0: Daniel@0: % Convert to int if format = 'native' Daniel@0: if strcmp(FMT,'native') Daniel@0: Y = int16((2^15)*Y); Daniel@0: end Daniel@0: Daniel@0: end Daniel@0: Daniel@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Daniel@0: function w = mysystem(cmd) Daniel@0: % Run system command; report error; strip all but last line Daniel@0: [s,w] = system(cmd); Daniel@0: if s ~= 0 Daniel@0: error(['unable to execute ',cmd,' (',w,')']); Daniel@0: end Daniel@0: % Keep just final line Daniel@0: w = w((1+max([0,findstr(w,10)])):end); Daniel@0: % Debug Daniel@0: %disp([cmd,' -> ','*',w,'*']); Daniel@0: Daniel@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Daniel@0: function a = tokenize(s) Daniel@0: % Break space-separated string into cell array of strings Daniel@0: % 2004-09-18 dpwe@ee.columbia.edu Daniel@0: a = []; Daniel@0: p = 1; Daniel@0: n = 1; Daniel@0: l = length(s); Daniel@0: nss = findstr([s(p:end),' '],' '); Daniel@0: for ns = nss Daniel@0: % Skip initial spaces Daniel@0: if ns == p Daniel@0: p = p+1; Daniel@0: else Daniel@0: if p <= l Daniel@0: a{n} = s(p:(ns-1)); Daniel@0: n = n+1; Daniel@0: p = ns+1; Daniel@0: end Daniel@0: end Daniel@0: end Daniel@0: